Compare commits

...
22 Commits
Author SHA1 Message Date
Burning_TNTandGlavo 84b3312ebb Fix #3825: 修复 NeoForge 愚人节版本无法正常解析的问题 (#3826)
* Fix #2825.

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-04-19 11:49:42 +08:00
Burning_TNTandGlavo f351d1d133 Close #3829: 将模组/整合包/资源包的默认下载源调整为 Modrinth (#3830)
* Close #3829: Use Modrinth as default download source.

* Update HMCLLocalizedDownloadListPage.java

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-04-18 22:49:02 +08:00
Glavo 5ca1120223 Bump jsoup to 1.19.1 (#3835) 2025-04-18 22:42:10 +08:00
Glavo 6aa4019221 Bump Gson to 2.13.0 (#3834) 2025-04-18 22:35:05 +08:00
Glavo 73fb8bda10 优化默认 JVM 参数 (#3824) 2025-04-15 03:46:33 +08:00
Glavo cc16f84992 默认限制离线账户功能 (#3823) 2025-04-14 20:34:01 +08:00
辞庐 b4945a150e 统一导出和导入整合包页面样式 (#3822) 2025-04-13 22:34:22 +08:00
Wulian233andGlavo 8bcbe74d82 优化整合包导出类型选择页面 (#3800)
* 优化整合包导出类型页面

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-04-12 22:10:28 +08:00
Glavo 69c2429e92 Bump mesa-loader-windows to 25.0.3 (#3819) 2025-04-12 20:36:05 +08:00
Glavo 88e83dc44a 修复自定义背景图片路径包含无效字符时启动器崩溃的问题 (#3817) 2025-04-12 17:51:28 +08:00
Burning_TNT 87c2126e5a Fix #3471: 模组管理内“安装到当前版本”与“下载到本地文件夹”相同 (#3772) 2025-04-12 17:39:58 +08:00
Liyan Zhao abf1ec3eb8 反序列化账户信息时优先将数字解析为 Long (#3080) 2025-04-12 13:01:49 +08:00
Burning_TNT 139fb41149 Fix #3147: 修复 LiteLoader 下载相关问题 (#3776)
* Fix #3147

* Fix: Use 'release' classifier on snapshot versions. Speed up loading time.

* Use https.
2025-04-12 12:07:26 +08:00
Glavo 6c986921d6 Delete unused constants (#3816) 2025-04-12 12:05:24 +08:00
3gf8jv4dv b8e6d6fc96 fix(locale): adjust the wording of the unofficial hmcl build prompts (#3813)
The display condition for `download.curseforge.unavailable` is whether
HMCL has a CurseForge API secret built in, not its update channel.
2025-04-12 11:39:40 +08:00
3gf8jv4dv 75e4aee8aa fix(locale): improve world backup ui i18n (#3815) 2025-04-12 11:38:50 +08:00
Glavo abda9f50e1 Fix #3807: 修复 Fabric Metadata 无效时 FabricInstallTask 抛出 NPE 的问题 (#3814) 2025-04-12 02:13:14 +08:00
Burning_TNT c33ef5170b Fix #3644: 修复下载 - 模组界面无法正常翻页、Category 会在切换下载源时出错的问题 (#3768) 2025-04-09 11:35:56 +08:00
Glavo a3cb871928 修复 RISC-V 平台游戏识别 CPU 名称出错的问题 (#3806) 2025-04-09 01:27:38 +08:00
Glavo 62e965c6fe 使用 Kala Compress 替代 commons-compress (#3481) 2025-04-08 17:36:36 +08:00
Glavo 99a031cea4 Gradle run task 应使用 HMCL_JAVA_HOME 所指向的 Java (#3804) 2025-04-08 12:29:14 +08:00
Zkitefly f97b9b9382 Update unlisted-versions.json (#3799) 2025-04-06 11:29:17 +08:00
47 changed files with 485 additions and 305 deletions

No files matched your search

+13 -3
View File
@@ -190,9 +190,9 @@ tasks.build {
dependsOn(makeExecutables)
}
fun parseToolOptions(options: String?): List<String> {
fun parseToolOptions(options: String?): MutableList<String> {
if (options == null)
return listOf()
return mutableListOf()
val builder = StringBuilder()
val result = mutableListOf<String>()
@@ -249,10 +249,20 @@ tasks.create<JavaExec>("run") {
workingDir = rootProject.rootDir
val vmOptions = parseToolOptions(System.getenv("HMCL_JAVA_OPTS"))
if (vmOptions.none { it.startsWith("-Dhmcl.offline.auth.restricted=") })
vmOptions += "-Dhmcl.offline.auth.restricted=false"
jvmArgs(vmOptions)
val hmclJavaHome = System.getenv("HMCL_JAVA_HOME")
if (hmclJavaHome != null) {
this.executable(file(hmclJavaHome).resolve("bin")
.resolve(if (System.getProperty("os.name").lowercase().startsWith("windows")) "java.exe" else "java"))
}
doFirst {
logger.quiet("HMCL_JAVA_OPTS: $vmOptions")
logger.quiet("HMCL_JAVA_OPTS: {}", vmOptions)
logger.quiet("HMCL_JAVA_HOME: {}", hmclJavaHome ?: System.getProperty("java.home"))
}
}
@@ -509,16 +509,16 @@ public class HMCLGameRepository extends DefaultGameRepository {
public static long getAllocatedMemory(long minimum, long available, boolean auto) {
if (auto) {
available -= 384 * 1024 * 1024; // Reserve 384MiB memory for off-heap memory and HMCL itself
available -= 512 * 1024 * 1024; // Reserve 512 MiB memory for off-heap memory and HMCL itself
if (available <= 0) {
return minimum;
}
final long threshold = 8L * 1024 * 1024 * 1024;
final long threshold = 8L * 1024 * 1024 * 1024; // 8 GiB
final long suggested = Math.min(available <= threshold
? (long) (available * 0.8)
: (long) (threshold * 0.8 + (available - threshold) * 0.2),
16384L * 1024 * 1024);
16L * 1024 * 1024 * 1024);
return Math.max(minimum, suggested);
} else {
return minimum;
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
@@ -64,7 +64,7 @@ public final class HMCLModpackProvider implements ModpackProvider {
}
@Override
public Modpack readManifest(ZipFile file, Path path, Charset encoding) throws IOException, JsonParseException {
public Modpack readManifest(ZipArchiveReader file, Path path, Charset encoding) throws IOException, JsonParseException {
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json");
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, HMCLModpack.class).setEncoding(encoding);
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json");
@@ -894,9 +894,6 @@ public final class LauncherHelper {
}
private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download/";
private static final String OPENJDK_DOWNLOAD_LINK = "https://learn.microsoft.com/java/openjdk/download";
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
public static void stopManagedProcesses() {
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.mod.*;
import org.jackhuang.hmcl.mod.curse.CurseModpackProvider;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;
@@ -83,7 +83,7 @@ public final class ModpackHelper {
}
public static Modpack readModpackManifest(Path file, Charset charset) throws UnsupportedModpackException, ManuallyCreatedModpackException {
try (ZipFile zipFile = CompressingUtils.openZipFile(file, charset)) {
try (ZipArchiveReader zipFile = CompressingUtils.openZipFile(file, charset)) {
// Order for trying detecting manifest is necessary here.
// Do not change to iterating providers.
for (ModpackProvider provider : new ModpackProvider[]{
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.java;
import org.apache.commons.compress.archivers.ArchiveEntry;
import kala.compress.archivers.ArchiveEntry;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.Hex;
@@ -22,6 +22,7 @@ import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.*;
@@ -50,6 +51,7 @@ import java.util.*;
import static java.util.stream.Collectors.toList;
import static javafx.collections.FXCollections.observableArrayList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.Lang.immutableListOf;
import static org.jackhuang.hmcl.util.Lang.mapOf;
@@ -277,6 +279,30 @@ public final class Accounts {
selected = accounts.get(0);
}
if (!globalConfig().isEnableOfflineAccount())
for (Account account : accounts) {
if (account instanceof MicrosoftAccount) {
globalConfig().setEnableOfflineAccount(true);
break;
}
}
if (!globalConfig().isEnableOfflineAccount())
accounts.addListener(new ListChangeListener<Account>() {
@Override
public void onChanged(Change<? extends Account> change) {
while (change.next()) {
for (Account account : change.getAddedSubList()) {
if (account instanceof MicrosoftAccount) {
accounts.removeListener(this);
globalConfig().setEnableOfflineAccount(true);
return;
}
}
}
}
});
selectedAccount.set(selected);
InvalidationListener listener = o -> {
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.setting;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.ToNumberPolicy;
import com.google.gson.annotations.SerializedName;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
@@ -60,6 +61,7 @@ public final class Config implements Observable {
.registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType
.registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy
.setPrettyPrinting()
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
.create();
@Nullable
@@ -21,7 +21,9 @@ import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
@@ -53,6 +55,8 @@ public final class GlobalConfig implements Observable {
private final IntegerProperty logRetention = new SimpleIntegerProperty();
private final BooleanProperty enableOfflineAccount = new SimpleBooleanProperty(false);
private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>());
private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>());
@@ -115,6 +119,18 @@ public final class GlobalConfig implements Observable {
this.logRetention.set(logRetention);
}
public boolean isEnableOfflineAccount() {
return enableOfflineAccount.get();
}
public BooleanProperty enableOfflineAccountProperty() {
return enableOfflineAccount;
}
public void setEnableOfflineAccount(boolean value) {
enableOfflineAccount.set(value);
}
public ObservableSet<String> getUserJava() {
return userJava;
}
@@ -129,7 +145,8 @@ public final class GlobalConfig implements Observable {
"platformPromptVersion",
"logRetention",
"userJava",
"disabledJava"
"disabledJava",
"enableOfflineAccount"
));
@Override
@@ -142,6 +159,9 @@ public final class GlobalConfig implements Observable {
jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion()));
jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion()));
jsonObject.add("logRetention", context.serialize(src.getLogRetention()));
if (src.enableOfflineAccount.get())
jsonObject.addProperty("enableOfflineAccount", true);
if (!src.getUserJava().isEmpty())
jsonObject.add("userJava", context.serialize(src.getUserJava()));
@@ -165,6 +185,7 @@ public final class GlobalConfig implements Observable {
config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0));
config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0));
config.setLogRetention(Optional.ofNullable(obj.get("logRetention")).map(JsonElement::getAsInt).orElse(20));
config.setEnableOfflineAccount(Optional.ofNullable(obj.get("enableOfflineAccount")).map(JsonElement::getAsBoolean).orElse(false));
JsonElement userJava = obj.get("userJava");
if (userJava != null && userJava.isJsonArray()) {
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXButton;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -44,14 +45,37 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import java.net.URI;
import java.time.ZoneId;
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;
public class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true);
static {
String property = System.getProperty("hmcl.offline.auth.restricted", "auto");
if ("false".equals(property)
|| "auto".equals(property) && "Asia/Shanghai".equals(ZoneId.systemDefault().getId())
|| globalConfig().isEnableOfflineAccount())
RESTRICTED.set(false);
else
globalConfig().enableOfflineAccountProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) {
if (newValue) {
globalConfig().enableOfflineAccountProperty().removeListener(this);
RESTRICTED.set(false);
}
}
});
}
private final ObservableList<AccountListItem> items;
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage")));
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
@@ -88,6 +112,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
private static class AccountListPageSkin extends DecoratorAnimatedPageSkin<AccountListPage> {
private final ObservableList<AdvancedListItem> authServerItems;
private ChangeListener<Boolean> holder;
public AccountListPageSkin(AccountListPage skinnable) {
super(skinnable);
@@ -96,24 +121,21 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
VBox boxMethods = new VBox();
{
boxMethods.getStyleClass().add("advanced-list-box-content");
boxMethods.getChildren().add(new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT)));
FXUtils.setLimitWidth(boxMethods, 200);
AdvancedListItem offlineItem = new AdvancedListItem();
offlineItem.getStyleClass().add("navigation-drawer-item");
offlineItem.setActionButtonVisible(false);
offlineItem.setTitle(i18n("account.methods.offline"));
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
boxMethods.getChildren().add(offlineItem);
AdvancedListItem microsoftItem = new AdvancedListItem();
microsoftItem.getStyleClass().add("navigation-drawer-item");
microsoftItem.setActionButtonVisible(false);
microsoftItem.setTitle(i18n("account.methods.microsoft"));
microsoftItem.setLeftGraphic(wrap(SVG.MICROSOFT));
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
boxMethods.getChildren().add(microsoftItem);
AdvancedListItem offlineItem = new AdvancedListItem();
offlineItem.getStyleClass().add("navigation-drawer-item");
offlineItem.setActionButtonVisible(false);
offlineItem.setTitle(i18n("account.methods.offline"));
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
VBox boxAuthServers = new VBox();
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
@@ -149,7 +171,29 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
return item;
});
Bindings.bindContent(boxAuthServers.getChildren(), authServerItems);
boxMethods.getChildren().add(boxAuthServers);
ClassTitle title = new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT));
if (RESTRICTED.get()) {
VBox wrapper = new VBox(offlineItem, boxAuthServers);
wrapper.setPadding(Insets.EMPTY);
FXUtils.installFastTooltip(wrapper, i18n("account.login.restricted"));
offlineItem.setDisable(true);
boxAuthServers.setDisable(true);
boxMethods.getChildren().setAll(title, microsoftItem, wrapper);
holder = FXUtils.onWeakChange(RESTRICTED, value -> {
if (!value) {
holder = null;
offlineItem.setDisable(false);
boxAuthServers.setDisable(false);
boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers);
}
});
} else {
boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers);
}
}
AdvancedListItem addAuthServerItem = new AdvancedListItem();
@@ -108,12 +108,17 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
public CreateAccountPane(AccountFactory<?> factory) {
if (factory == null) {
showMethodSwitcher = true;
String preferred = config().getPreferredLoginType();
try {
factory = Accounts.getAccountFactory(preferred);
} catch (IllegalArgumentException e) {
factory = Accounts.FACTORY_OFFLINE;
if (AccountListPage.RESTRICTED.get()) {
showMethodSwitcher = false;
factory = Accounts.FACTORY_MICROSOFT;
} else {
showMethodSwitcher = true;
String preferred = config().getPreferredLoginType();
try {
factory = Accounts.getAccountFactory(preferred);
} catch (IllegalArgumentException e) {
factory = Accounts.FACTORY_OFFLINE;
}
}
} else {
showMethodSwitcher = false;
@@ -203,7 +203,11 @@ public class DecoratorController {
case CUSTOM:
String backgroundImage = config().getBackgroundImage();
if (backgroundImage != null)
image = tryLoadImage(Paths.get(backgroundImage));
try {
image = tryLoadImage(Paths.get(backgroundImage));
} catch (Exception e) {
LOG.warning("Couldn't load background image", e);
}
break;
case NETWORK:
String backgroundImageUrl = config().getBackgroundImageUrl();
@@ -130,7 +130,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
};
}
private static void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
public static void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
if (version == null) version = profile.getSelectedVersion();
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
@@ -43,12 +43,15 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
public ModpackTypeSelectionPage(WizardController controller) {
this.controller = controller;
this.setPadding(new Insets(10));
Label title = new Label(i18n("modpack.export.as"));
title.setPadding(new Insets(8));
VBox.setMargin(title, new Insets(8, 0, 8, 12));
this.getStyleClass().add("jfx-list-view");
this.setMaxSize(300, 150);
this.setMaxSize(400, 150);
this.setSpacing(8);
this.getChildren().setAll(
title,
createButton(MODPACK_TYPE_MCBBS, McbbsModpackExportTask.OPTION),
@@ -59,6 +62,8 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
private JFXButton createButton(String type, ModpackExportInfo.Options option) {
JFXButton button = new JFXButton();
button.getStyleClass().add("card");
button.setOnAction(e -> {
controller.getSettings().put(MODPACK_TYPE, type);
controller.getSettings().put(MODPACK_INFO_OPTION, option);
@@ -22,7 +22,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.apache.commons.compress.utils.BoundedInputStream;
import kala.compress.utils.BoundedInputStream;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.*;
@@ -60,10 +60,7 @@ import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
@@ -156,7 +153,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
FXUtils.runInFX(() -> selectedVersion.set(versionID));
}
public void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort) {
private void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort) {
retrySearch = null;
setLoading(true);
setFailed(false);
@@ -171,7 +168,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("")
: "";
}
}).thenApplyAsync(gameVersion -> repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)).whenComplete(Schedulers.javafx(), (result, exception) -> {
}).thenApplyAsync(
gameVersion -> repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)
).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (searchID != currentSearchID) {
return;
}
@@ -312,7 +311,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
StackPane categoryStackPane = new StackPane();
JFXComboBox<CategoryIndented> categoryComboBox = new JFXComboBox<>();
categoryComboBox.getItems().setAll(new CategoryIndented(0, null));
categoryComboBox.getItems().setAll(CategoryIndented.ALL);
categoryStackPane.getChildren().setAll(categoryComboBox);
categoryComboBox.prefWidthProperty().bind(categoryStackPane.widthProperty());
categoryComboBox.getStyleClass().add("fit-width");
@@ -320,14 +319,22 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
categoryComboBox.getSelectionModel().select(0);
categoryComboBox.setConverter(stringConverter(getSkinnable()::getLocalizedCategoryIndent));
FXUtils.onChangeAndOperate(getSkinnable().downloadSource, downloadSource -> {
categoryComboBox.getItems().setAll(CategoryIndented.ALL);
categoryComboBox.getSelectionModel().select(0);
Task.supplyAsync(() -> getSkinnable().repository.getCategories())
.thenAcceptAsync(Schedulers.javafx(), categories -> {
if (!Objects.equals(getSkinnable().downloadSource.get(), downloadSource)) {
return;
}
List<CategoryIndented> result = new ArrayList<>();
result.add(new CategoryIndented(0, null));
result.add(CategoryIndented.ALL);
for (RemoteModRepository.Category category : Lang.toIterable(categories)) {
resolveCategory(category, 0, result);
}
categoryComboBox.getItems().setAll(result);
categoryComboBox.getSelectionModel().select(0);
}).start();
});
@@ -344,7 +351,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
IntegerProperty filterID = new SimpleIntegerProperty(this, "Filter ID", 0);
IntegerProperty currentFilterID = new SimpleIntegerProperty(this, "Current Filter ID", -1);
EventHandler<ActionEvent> searchAction = e -> {
if (currentFilterID.get() != filterID.get()) {
if (currentFilterID.get() != -1 && currentFilterID.get() != filterID.get()) {
control.pageOffset.set(0);
}
currentFilterID.set(filterID.get());
@@ -379,8 +386,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
JFXButton firstPageButton = FXUtils.newBorderButton(i18n("search.first_page"));
firstPageButton.setOnAction(event -> {
control.pageOffset.set(0);
changeButton.value.run();
searchAction.handle(event);
changeButton.value.run();
});
JFXButton previousPageButton = FXUtils.newBorderButton(i18n("search.previous_page"));
@@ -388,8 +395,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
int pageOffset = control.pageOffset.get();
if (pageOffset > 0) {
control.pageOffset.set(pageOffset - 1);
changeButton.value.run();
searchAction.handle(event);
changeButton.value.run();
}
});
@@ -404,16 +411,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
int nv = control.pageOffset.get() + 1;
if (nv < control.pageCount.get()) {
control.pageOffset.set(nv);
changeButton.value.run();
searchAction.handle(event);
changeButton.value.run();
}
});
JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page"));
lastPageButton.setOnAction(event -> {
control.pageOffset.set(control.pageCount.get() - 1);
changeButton.value.run();
searchAction.handle(event);
changeButton.value.run();
});
firstPageButton.setDisable(true);
@@ -529,6 +536,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
}
private static class CategoryIndented {
private static final CategoryIndented ALL = new CategoryIndented(0, null);
private final int indent;
private final RemoteModRepository.Category category;
@@ -479,14 +479,17 @@ public class DownloadPage extends Control implements DecoratorPage {
this.setBody(box);
JFXButton downloadButton = new JFXButton(type == RemoteModRepository.Type.MODPACK ? i18n("install.modpack") : i18n("mods.install"));
downloadButton.getStyleClass().add("dialog-accept");
downloadButton.setOnAction(e -> {
if (type == RemoteModRepository.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
fireEvent(new DialogCloseEvent());
}
selfPage.download(version);
});
JFXButton downloadButton = null;
if (selfPage.callback != null) {
downloadButton = new JFXButton(type == RemoteModRepository.Type.MODPACK ? i18n("install.modpack") : i18n("mods.install"));
downloadButton.getStyleClass().add("dialog-accept");
downloadButton.setOnAction(e -> {
if (type == RemoteModRepository.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
fireEvent(new DialogCloseEvent());
}
selfPage.download(version);
});
}
JFXButton saveAsButton = new JFXButton(i18n("mods.save_as"));
saveAsButton.getStyleClass().add("dialog-accept");
@@ -501,7 +504,11 @@ public class DownloadPage extends Control implements DecoratorPage {
cancelButton.getStyleClass().add("dialog-cancel");
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
this.setActions(downloadButton, saveAsButton, cancelButton);
if (downloadButton == null) {
this.setActions(saveAsButton, cancelButton);
} else {
this.setActions(downloadButton, saveAsButton, cancelButton);
}
this.prefWidthProperty().bind(BindingMapping.of(Controllers.getStage().widthProperty()).map(w -> w.doubleValue() * 0.7));
this.prefHeightProperty().bind(BindingMapping.of(Controllers.getStage().heightProperty()).map(w -> w.doubleValue() * 0.7));
@@ -55,11 +55,11 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
repository = new Repository(type, curseForge, modrinth);
supportChinese.set(true);
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
if (curseForge != null) {
downloadSource.set("mods.curseforge");
} else if (modrinth != null) {
downloadSources.get().setAll("mods.modrinth", "mods.curseforge");
if (modrinth != null) {
downloadSource.set("mods.modrinth");
} else if (curseForge != null) {
downloadSource.set("mods.curseforge");
} else {
throw new AssertionError("Should not be here.");
}
@@ -110,7 +110,7 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
try {
return I18n.getResourceBundle().getString(key);
} catch (MissingResourceException e) {
LOG.warning("Cannot find key " + key + " in resource bundle", e);
LOG.warning("Cannot find key " + key + " in resource bundle");
return category;
}
}
@@ -446,7 +446,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
repository instanceof CurseForgeRemoteModRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false),
remoteMod,
new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getVersionId()),
null
(profile, version, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(profile, version, file, "mods")
));
});
button.setDisable(false);
@@ -140,7 +140,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
}
void createBackup() {
Controllers.taskDialog(new WorldBackupTask(world, backupsDir).setName(i18n("world.backup")).thenApplyAsync(path -> {
Controllers.taskDialog(new WorldBackupTask(world, backupsDir).setName(i18n("world.backup.processing")).thenApplyAsync(path -> {
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
if (!matcher.matches()) {
throw new AssertionError("Wrong backup file name" + path);
@@ -176,7 +176,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
@Override
protected List<Node> initializeToolbar(WorldBackupsPage skinnable) {
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.backup"), SVG.ARCHIVE, skinnable::createBackup));
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.backup.create.new_one"), SVG.ARCHIVE, skinnable::createBackup));
}
}
@@ -88,6 +88,7 @@ account.login.skip=Log in offline
account.login.retry=Retry
account.login.refresh=Log in again
account.login.refresh.microsoft.hint=You need to log in to your Microsoft account again because the account authorization is invalid.
account.login.restricted=Sign in to your Microsoft account to enable this feature
account.logout=Logout
account.register=Register
account.manage=Account List
@@ -328,7 +329,7 @@ download=Download
download.hint=Install games and modpacks or download mods, resource packs, and worlds.
download.code.404=File "%s" not found on the remote server.
download.content=Addons
download.curseforge.unavailable=HMCL Nightly channel build does not support access to CurseForge. Please use Release or Beta channel builds to download.
download.curseforge.unavailable=This HMCL build does not support access to CurseForge. Please use the official build to access CurseForge.
download.existing=The file cannot be saved because it already exists. You can click "Save As" to save the file elsewhere.
download.external_link=Visit Download Website
download.failed=Failed to download "%1$s", response code: %2$d.
@@ -1047,11 +1048,13 @@ web.view_in_browser=View in browser
world=Worlds
world.add=Add World
world.backup=Backup World
world.backup=World Backup
world.backup.create.new_one=Create New Backup
world.backup.create.failed=Failed to create backup.\n%s
world.backup.create.locked=The world is currently in use. Please close the game and try again.
world.backup.create.success=Successfully created a new backup: %s
world.backup.delete=Delete this backup
world.backup.processing=Backing up ...
world.backup.reveal=Show in folder
world.backup.title=World [%s] - Backups
world.datapack=Manage Datapacks
@@ -328,7 +328,7 @@ download=Descargar
download.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos y mundos.
download.code.404=Archivo no encontrado en el servidor remoto: %s
download.content=Complementos
download.curseforge.unavailable=La versión Nightly de HMCL no admite el acceso a CurseForge. Utilice las versiones Release o Beta para descargarlas.
download.curseforge.unavailable=Esta versión de HMCL no permite acceder a CurseForge. Utilice la versión oficial para acceder a CurseForge.
download.existing=El archivo no se puede guardar porque ya existe. Puedes hacer clic en «Guardar como» para guardar el archivo en otro lugar.
download.external_link=Abrir sitio web
download.failed=Falló la descarga de %1$s, código de respuesta: %2$d.
@@ -1052,10 +1052,12 @@ web.view_in_browser=Ver en navegador
world=Mundos
world.add=Añadir mundo
world.backup=Copia de seguridad
world.backup.create.new_one=Crear uno nuevo
world.backup.create.failed=No se ha podido crear la copia de seguridad.\n%s
world.backup.create.locked=El mundo está actualmente en uso. Por favor, cierra el juego e inténtalo de nuevo.
world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s
world.backup.delete=Eliminar esta copia de seguridad
world.backup.processing=Creando nueva copia de seguridad ...
world.backup.reveal=Abrir el directorio
world.backup.title=Mundo [%s] - Copias de seguridad
world.datapack=Gestionar paquetes de datos
@@ -329,7 +329,7 @@ download=Скачать
download.hint=Установить игры и модпаки или скачать моды, пакеты ресурсов и миры.
download.code.404=Файл «%s» не найден на удаленном сервере.
download.content=Аддоны
download.curseforge.unavailable=Альфа-версия HMCL не поддерживает доступ к CurseForge. Для скачивания используйте версии релиза или бета.
download.curseforge.unavailable=Эта сборка HMCL не поддерживает доступ к CurseForge. Пожалуйста, используйте официальную сборку для доступа к CurseForge.
download.existing=Файл существует и по этому не может быть сохранён. Можно использовать «Сохранить как», чтобы сохранить файл в другом месте.
download.external_link=Открыть сайт
download.failed=Не удалось скачать «%1$s», код ответа: %2$d.
@@ -1051,10 +1051,12 @@ web.view_in_browser=Смотреть в браузере
world=Миры
world.add=Добавить мир
world.backup=Резервный мир
world.backup.create.new_one=Создать новый
world.backup.create.failed=Не удалось создать резервную копию.\n%s
world.backup.create.locked=В настоящее время мир находится в эксплуатации. Закройте игру и попробуйте снова.
world.backup.create.success=Успешно создано новое резервное копирование: %s
world.backup.delete=Удалить эту резервную копию
world.backup.processing=Создание новой резервной копии ...
world.backup.reveal=Показать в диспетчере файлов
world.backup.title=Мир [%s] - Резервные копии
world.datapack=Управлять наборами данных
@@ -91,6 +91,7 @@ account.login.skip=跳過重新整理帳戶
account.login.retry=再次重新整理帳戶
account.login.refresh=重新登入
account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新加入 Microsoft 帳戶
account.login.restricted=登入微軟帳戶以啟用此功能
account.logout=登出
account.register=註冊
account.manage=帳戶清單
@@ -330,7 +331,7 @@ download=下載
download.hint=安裝遊戲和模組包或下載模組、資源包和地圖
download.code.404=遠端伺服器沒有需要下載的檔案:%s
download.content=遊戲內容
download.curseforge.unavailable=HMCL 預覽版暫不支援訪問 CurseForge,請使用穩定版或開發版進行下載。
download.curseforge.unavailable=這個 HMCL 版本不支援訪問 CurseForge。請使用官方版本進行下載。
download.existing=檔案已存在,無法儲存。你可以將檔案儲存至其他地方。
download.external_link=打開下載網站
download.failed=下載失敗:%1$s\n錯誤碼:%2$d
@@ -670,7 +671,7 @@ modpack.download=下載模組包
modpack.download.title=模組包下載 - %1s
modpack.enter_name=給遊戲取個你喜歡的名稱
modpack.export=匯出模組包
modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBBS 類型。
modpack.export.as=請選取模組包類型
modpack.file_api=模組包下載連結前綴
modpack.files.blueprints=BuildCraft 藍圖
modpack.files.config=模組設定檔案
@@ -703,7 +704,7 @@ modpack.type.curse=Curse
modpack.type.curse.error=無法完成該模組包所需的相依元件下載,請多次重試或設定代理……
modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該模組包的最新版本或者安裝其他模組包。
modpack.type.manual.warning=該模組包由發佈者手動打包,其中可能已經包含啟動器,建議嘗試解壓後使用其內建的啟動器執行遊戲。\nHMCL 可以嘗試匯入該模組包,但不保證可用性,是否繼續?
modpack.type.mcbbs=MCBBS
modpack.type.mcbbs=MCBBS 模組包
modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher 匯入
modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
@@ -853,11 +854,13 @@ web.view_in_browser=在瀏覽器中查看
world=世界
world.add=加入世界
world.backup=備份世界
world.backup=世界備份
world.backup.create.new_one=建立新備份
world.backup.create.failed=創建備份失敗。\n%s
world.backup.create.locked=該世界正在使用中,請關閉遊戲後重試。
world.backup.create.success=成功創建新備份:%s
world.backup.delete=删除此備份
world.backup.processing=正在備份中……
world.backup.title=世界 [%s] - 備份
world.datapack=管理資料包
world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支援資料包
@@ -92,6 +92,7 @@ account.login.skip=跳过账户刷新
account.login.retry=再次刷新账户
account.login.refresh=重新登录
account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。
account.login.restricted=登录微软账户以启用此功能
account.logout=登出
account.register=注册
account.manage=账户列表
@@ -339,7 +340,7 @@ download=下载
download.hint=安装游戏和整合包或下载模组、资源包和地图
download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。
download.content=游戏内容
download.curseforge.unavailable=HMCL 预览版暂不支持访问 CurseForge,请使用稳定版或开发版进行下载。
download.curseforge.unavailable=此 HMCL 版本不支持访问 CurseForge。请使用官方版本进行下载。
download.existing=文件已存在,无法保存。你可以将文件保存至其他地方。
download.external_link=打开下载网站
download.failed=下载失败: %1$s,\n错误码:%2$d\n你可以点击右上角帮助按钮进行求助。
@@ -681,7 +682,7 @@ modpack.download=下载整合包
modpack.download.title=整合包下载 - %1s
modpack.enter_name=给游戏起个你喜欢的名字
modpack.export=导出整合包
modpack.export.as=请选择整合包类型 (若无法决定,请选择“我的世界中文论坛整合包标准”)
modpack.export.as=请选择整合包类型
modpack.file_api=整合包下载链接前缀
modpack.files.blueprints=BuildCraft 蓝图
modpack.files.config=模组配置文件
@@ -714,7 +715,7 @@ modpack.type.curse=Curse
modpack.type.curse.error=未能完成该整合包所需的依赖下载,请再次尝试或设置代理。\n你可以点击右上角帮助按钮进行求助。
modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
modpack.type.manual.warning=该整合包由发布者手动打包,其中可能已经包含启动器,建议尝试解压后使用其自带的启动器运行游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。\nHMCL 可以尝试导入该整合包,但不保证可用性,是否继续?
modpack.type.mcbbs=我的世界中文论坛整合包标准
modpack.type.mcbbs=MCBBS 整合包 (推荐)
modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher 导入
modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
@@ -864,11 +865,13 @@ web.view_in_browser=在浏览器中查看
world=世界
world.add=添加世界
world.backup=备份世界
world.backup=世界备份
world.backup.create.new_one=创建新备份
world.backup.create.failed=创建备份失败。\n%s
world.backup.create.locked=该世界正在使用中,请关闭游戏后重试。
world.backup.create.success=成功创建新备份:%s
world.backup.delete=删除此备份
world.backup.processing=正在备份中……
world.backup.title=世界 [%s] - 备份
world.datapack=管理数据包
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包
+26 -15
View File
@@ -3979,6 +3979,17 @@
}
}
},
"com.github.oshi:oshi-core:6.6.5": {
"name": "com.github.oshi:oshi-core:6.8.0",
"downloads": {
"artifact": {
"path": "com/github/oshi/oshi-core/6.8.0/oshi-core-6.8.0.jar",
"url": "https://repo1.maven.org/maven2/com/github/oshi/oshi-core/6.8.0/oshi-core-6.8.0.jar",
"sha1": "004d12fac84286063e6842688610b8b2d43924a4",
"size": 1011309
}
}
},
"net.java.jinput:jinput-platform:2.0.5:natives": null,
"com.mojang:text2speech:1.10.3:natives": null,
"com.mojang:text2speech:1.11.3:natives": null,
@@ -3987,26 +3998,26 @@
},
"windows-x86_64": {
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:0.3.0:x64",
"name": "org.glavo:mesa-loader-windows:25.0.3:x64",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar",
"sha1": "629fca32417d6ec489cef8b2cbd0827131ec6801",
"size": 27174940
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x64.jar",
"sha1": "b2552fcc8c98e95b4559c39f2ff87cdb3aaaa513",
"size": 27971214
}
}
}
},
"windows-x86": {
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:0.3.0:x86",
"name": "org.glavo:mesa-loader-windows:25.0.3:x86",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar",
"sha1": "d25e0cdf5c5eb182acc9b93f700e0e6d8de36283",
"size": 22528549
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x86.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x86.jar",
"sha1": "6fd0fd7da88ca6636f735e1e0f6feb7f62f59715",
"size": 23311299
}
}
}
@@ -4389,13 +4400,13 @@
"com.mojang:text2speech:1.12.4:natives": null,
"com.mojang:text2speech:1.13.9:natives-windows": null,
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:0.3.0:arm64",
"name": "org.glavo:mesa-loader-windows:25.0.3:arm64",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar",
"sha1": "1986490c6fbe950e64018c2fb62c8ecf77a247ed",
"size": 24082103
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-arm64.jar",
"sha1": "40ca5de3351bf48b7b9efa5d2598d5b6b4d1ab8e",
"size": 24905270
}
}
}
+6 -3
View File
@@ -3,16 +3,19 @@ plugins {
}
dependencies {
val kalaCompressVersion = "1.27.1-1"
api("org.glavo.kala:kala-compress-archivers-zip:$kalaCompressVersion")
api("org.glavo.kala:kala-compress-archivers-tar:$kalaCompressVersion")
api("org.glavo:simple-png-javafx:0.3.0")
api("com.google.code.gson:gson:2.12.1")
api("com.google.code.gson:gson:2.13.0")
api("com.moandjiezana.toml:toml4j:0.7.2")
api("org.tukaani:xz:1.10")
api("org.hildan.fxgson:fx-gson:5.0.0")
api("org.jenkins-ci:constant-pool-scanner:1.2")
api("com.github.steveice10:opennbt:1.5")
api("org.nanohttpd:nanohttpd:2.3.1")
api("org.apache.commons:commons-compress:1.25.0")
api("org.jsoup:jsoup:1.18.3")
api("org.jsoup:jsoup:1.19.1")
compileOnlyApi("org.jetbrains:annotations:26.0.1")
if (JavaVersion.current().isJava8) {
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.IOException;
import java.util.*;
import static org.jackhuang.hmcl.download.UnsupportedInstallationException.FABRIC_NOT_COMPATIBLE_WITH_FORGE;
@@ -83,8 +84,12 @@ public final class FabricInstallTask extends Task<Version> {
}
@Override
public void execute() {
setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), FabricInfo.class), remote.getGameVersion(), remote.getSelfVersion()));
public void execute() throws IOException {
FabricInfo fabricInfo = JsonUtils.GSON.fromJson(launchMetaTask.getResult(), FabricInfo.class);
if (fabricInfo == null)
throw new IOException("Fabric metadata is invalid");
setResult(getPatch(fabricInfo, remote.getGameVersion(), remote.getSelfVersion()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));
}
@@ -18,21 +18,16 @@
package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
*
* @author huangyuhui
*/
public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemoteVersion> {
@@ -47,66 +42,44 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
return false;
}
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
if (branch == null || repository == null)
return;
private static final class LiteLoaderBMCLVersion {
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
private final LiteLoaderVersion build;
private final String version;
String version = v.getVersion();
String url = "https://bmclapi2.bangbang93.com/liteloader/download?version=" + version;
if (snapshot) {
try {
version = version.replace("SNAPSHOT", getLatestSnapshotVersion(repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/"));
url = repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/liteloader-" + version + "-release.jar";
} catch (Exception ignore) {
}
}
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, Collections.singletonList(url),
v.getTweakClass(), v.getLibraries()
));
public LiteLoaderBMCLVersion(LiteLoaderVersion build, String version) {
this.build = build;
this.version = version;
}
}
@Override
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(downloadProvider.injectURL(LITELOADER_LIST)).getJsonAsync(LiteLoaderVersionsRoot.class)
.thenAcceptAsync(root -> {
lock.writeLock().lock();
throw new UnsupportedOperationException();
}
@Override
public CompletableFuture<?> refreshAsync(String gameVersion) {
return HttpRequest.GET(
downloadProvider.injectURL("https://bmclapi2.bangbang93.com/liteloader/list"), Pair.pair("mcversion", gameVersion)
)
.getJsonAsync(LiteLoaderBMCLVersion.class)
.thenAccept(v -> {
lock.writeLock().lock();
try {
versions.clear();
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
String gameVersion = entry.getKey();
LiteLoaderGameVersions liteLoader = entry.getValue();
String gg = VersionNumber.normalize(gameVersion);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
}
versions.put(gameVersion, new LiteLoaderRemoteVersion(
gameVersion, v.version, RemoteVersion.Type.UNCATEGORIZED,
Collections.singletonList(NetworkUtils.withQuery(
downloadProvider.injectURL("https://bmclapi2.bangbang93.com/liteloader/download"),
Collections.singletonMap("version", v.version)
)),
v.build.getTweakClass(), v.build.getLibraries()
));
} finally {
lock.writeLock().unlock();
}
});
}
public static final String LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json";
private static String getLatestSnapshotVersion(String repo) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(repo + "maven-metadata.xml");
Element r = doc.getDocumentElement();
Element snapshot = (Element) r.getElementsByTagName("snapshot").item(0);
Node timestamp = snapshot.getElementsByTagName("timestamp").item(0);
Node buildNumber = snapshot.getElementsByTagName("buildNumber").item(0);
return timestamp.getTextContent() + "-" + buildNumber.getTextContent();
}
}
@@ -30,6 +30,7 @@ import java.util.List;
public class LiteLoaderRemoteVersion extends RemoteVersion {
private final String tweakClass;
private final Collection<Library> libraries;
/**
* Constructor.
*
@@ -37,8 +38,8 @@ public class LiteLoaderRemoteVersion extends RemoteVersion {
* @param selfVersion the version string of the remote version.
* @param urls the installer or universal jar original URL.
*/
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, List<String> urls, String tweakClass, Collection<Library> libraries) {
super(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), gameVersion, selfVersion, null, urls);
LiteLoaderRemoteVersion(String gameVersion, String selfVersion, Type type, List<String> urls, String tweakClass, Collection<Library> libraries) {
super(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), gameVersion, selfVersion, null, type, urls);
this.tweakClass = tweakClass;
this.libraries = libraries;
@@ -18,21 +18,20 @@
package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
/**
*
* @author huangyuhui
*/
public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVersion> {
@@ -45,52 +44,39 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
@Override
public boolean hasType() {
return false;
return true;
}
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
if (branch == null || repository == null)
return;
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
String version = v.getVersion();
String url = repository.getUrl() + "com/mumfrey/liteloader/" + gameVersion + "/" + v.getFile();
if (snapshot) {
try {
version = version.replace("SNAPSHOT", getLatestSnapshotVersion(repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/"));
url = repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/liteloader-" + version + "-release.jar";
} catch (Exception ignore) {
}
}
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, Collections.singletonList(url),
v.getTweakClass(), v.getLibraries()
));
}
}
public static final String LITELOADER_LIST = "https://dl.liteloader.com/versions/versions.json";
@Override
public CompletableFuture<?> refreshAsync() {
public CompletableFuture<?> refreshAsync(String gameVersion) {
return HttpRequest.GET(downloadProvider.injectURL(LITELOADER_LIST)).getJsonAsync(LiteLoaderVersionsRoot.class)
.thenAcceptAsync(root -> {
LiteLoaderGameVersions versions = root.getVersions().get(gameVersion);
if (versions == null) {
return;
}
LiteLoaderRemoteVersion snapshot = null;
if (versions.getSnapshots() != null) {
try {
snapshot = loadSnapshotVersion(gameVersion, versions.getSnapshots().getLiteLoader().get("latest"));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
lock.writeLock().lock();
try {
versions.clear();
this.versions.clear();
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
String gameVersion = entry.getKey();
LiteLoaderGameVersions liteLoader = entry.getValue();
if (versions.getRepoitory() != null && versions.getArtifacts() != null) {
loadArtifactVersion(gameVersion, versions.getRepoitory(), versions.getArtifacts());
}
String gg = VersionNumber.normalize(gameVersion);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
if (snapshot != null) {
this.versions.put(gameVersion, snapshot);
}
} finally {
lock.writeLock().unlock();
@@ -98,16 +84,40 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
});
}
public static final String LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json";
@Override
public CompletableFuture<?> refreshAsync() {
throw new UnsupportedOperationException();
}
private static String getLatestSnapshotVersion(String repo) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(repo + "maven-metadata.xml");
Element r = doc.getDocumentElement();
Element snapshot = (Element) r.getElementsByTagName("snapshot").item(0);
Node timestamp = snapshot.getElementsByTagName("timestamp").item(0);
Node buildNumber = snapshot.getElementsByTagName("buildNumber").item(0);
return timestamp.getTextContent() + "-" + buildNumber.getTextContent();
private void loadArtifactVersion(String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch) {
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
versions.put(gameVersion, new LiteLoaderRemoteVersion(
gameVersion, v.getVersion(), RemoteVersion.Type.RELEASE,
Collections.singletonList(repository.getUrl() + "com/mumfrey/liteloader/" + gameVersion + "/" + v.getFile()),
v.getTweakClass(), v.getLibraries()
));
}
}
// Workaround for https://github.com/HMCL-dev/HMCL/issues/3147: Some LiteLoader artifacts aren't published on http://dl.liteloader.com/repo
private static final String SNAPSHOT_METADATA = "https://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/%s-SNAPSHOT/maven-metadata.xml";
private static final String SNAPSHOT_FILE = "https://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/%s-SNAPSHOT/liteloader-%s-%s-%s-release.jar";
private LiteLoaderRemoteVersion loadSnapshotVersion(String gameVersion, LiteLoaderVersion v) throws IOException {
String root = HttpRequest.GET(String.format(SNAPSHOT_METADATA, gameVersion)).getString();
Document document = Jsoup.parseBodyFragment(root);
String timestamp = Objects.requireNonNull(document.select("timestamp"), "timestamp").text();
String buildNumber = Objects.requireNonNull(document.select("buildNumber"), "buildNumber").text();
return new LiteLoaderRemoteVersion(
gameVersion, timestamp + "-" + buildNumber, RemoteVersion.Type.SNAPSHOT,
Collections.singletonList(String.format(SNAPSHOT_FILE, gameVersion, gameVersion, timestamp, buildNumber)),
v.getTweakClass(), v.getLibraries()
);
}
}
@@ -10,6 +10,7 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static org.jackhuang.hmcl.util.Lang.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemoteVersion> {
private final DownloadProvider downloadProvider;
@@ -56,8 +57,20 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
}
for (String version : results[1].versions) {
int si1 = version.indexOf('.'), si2 = version.indexOf('.', version.indexOf('.') + 1);
String mcVersion = "1." + version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);
String mcVersion;
try {
int si1 = version.indexOf('.'), si2 = version.indexOf('.', version.indexOf('.') + 1);
int majorVersion = Integer.parseInt(version.substring(0, si1));
if (majorVersion == 0) { // Snapshot version.
mcVersion = version.substring(si1 + 1, si2);
} else {
mcVersion = "1." + version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);
}
} catch (RuntimeException e) {
LOG.warning(String.format("Cannot parse NeoForge version %s for cracking its mc version.", version), e);
continue;
}
versions.put(mcVersion, new NeoForgeRemoteVersion(
mcVersion, NeoForgeRemoteVersion.normalize(version),
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.java;
import org.apache.commons.compress.archivers.ArchiveEntry;
import kala.compress.archivers.ArchiveEntry;
import org.jackhuang.hmcl.util.KeyValuePairProperties;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
@@ -189,30 +189,57 @@ public class DefaultLauncher extends Launcher {
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
res.addDefault("-Duser.home=", options.getGameDir().getParent());
final int javaVersion = options.getJava().getParsedVersion();
final boolean is64bit = options.getJava().getBits() == Bits.BIT_64;
res.addUnstableDefault("UnlockExperimentalVMOptions", true);
res.addUnstableDefault("UnlockDiagnosticVMOptions", true);
// Using G1GC with its settings by default
if (options.getJava().getParsedVersion() >= 8
if (javaVersion >= 8
&& res.noneMatch(arg -> "-XX:-UseG1GC".equals(arg) || (arg.startsWith("-XX:+Use") && arg.endsWith("GC")))) {
res.addUnstableDefault("UnlockExperimentalVMOptions", true);
res.addUnstableDefault("UseG1GC", true);
res.addUnstableDefault("G1MixedGCCountTarget", "5");
res.addUnstableDefault("G1NewSizePercent", "20");
res.addUnstableDefault("G1ReservePercent", "20");
res.addUnstableDefault("MaxGCPauseMillis", "50");
res.addUnstableDefault("G1HeapRegionSize", "32m");
}
res.addUnstableDefault("UseAdaptiveSizePolicy", false);
res.addUnstableDefault("OmitStackTraceInFastThrow", false);
res.addUnstableDefault("DontCompileHugeMethods", false);
// JIT Options
if (javaVersion <= 8) {
res.addUnstableDefault("MaxInlineLevel", "15");
}
if (is64bit && OperatingSystem.TOTAL_MEMORY > 4 * 1024) {
res.addUnstableDefault("DontCompileHugeMethods", false);
res.addUnstableDefault("MaxNodeLimit", "240000");
res.addUnstableDefault("NodeLimitFudgeFactor", "8000");
res.addUnstableDefault("TieredCompileTaskTimeout", "10000");
res.addUnstableDefault("ReservedCodeCacheSize", "400M");
if (javaVersion >= 9) {
res.addUnstableDefault("NonNMethodCodeHeapSize", "12M");
res.addUnstableDefault("ProfiledCodeHeapSize", "194M");
}
if (javaVersion >= 8) {
res.addUnstableDefault("NmethodSweepActivity", "1");
}
}
// As 32-bit JVM allocate 320KB for stack by default rather than 64-bit version allocating 1MB,
// causing Minecraft 1.13 crashed accounting for java.lang.StackOverflowError.
if (options.getJava().getBits() == Bits.BIT_32) {
if (!is64bit) {
res.addDefault("-Xss", "1m");
}
if (options.getJava().getParsedVersion() == 16)
if (javaVersion == 16)
res.addDefault("--illegal-access=", "permit");
if (javaVersion == 24 || javaVersion == 25)
res.addDefault("--sun-misc-unsafe-memory-access=", "allow");
res.addDefault("-Dfml.ignoreInvalidMinecraftCertificates=", "true");
res.addDefault("-Dfml.ignorePatchDiscrepancies=", "true");
}
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.mod;
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.LaunchOptions;
import org.jackhuang.hmcl.task.Task;
@@ -44,7 +44,7 @@ public interface ModpackProvider {
* @throws JsonParseException if the manifest.json is missing or malformed.
* @return the manifest.
*/
Modpack readManifest(ZipFile zipFile, Path file, Charset encoding) throws IOException, JsonParseException;
Modpack readManifest(ZipArchiveReader zipFile, Path file, Charset encoding) throws IOException, JsonParseException;
default void injectLaunchOptions(String modpackConfigurationJson, LaunchOptions.Builder builder) {
}
@@ -102,7 +102,9 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
@Override
public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
int categoryId = 0;
if (category != null) categoryId = ((CurseAddon.Category) category.getSelf()).getId();
if (category != null && category.getSelf() instanceof CurseAddon.Category) {
categoryId = ((CurseAddon.Category) category.getSelf()).getId();
}
Response<List<CurseAddon>> response = HttpRequest.GET(PREFIX + "/v1/mods/search",
pair("gameId", "432"),
pair("classId", Integer.toString(section)),
@@ -18,8 +18,8 @@
package org.jackhuang.hmcl.mod.curse;
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
@@ -57,7 +57,7 @@ public final class CurseModpackProvider implements ModpackProvider {
}
@Override
public Modpack readManifest(ZipFile zip, Path file, Charset encoding) throws IOException, JsonParseException {
public Modpack readManifest(ZipArchiveReader zip, Path file, Charset encoding) throws IOException, JsonParseException {
CurseManifest manifest = JsonUtils.fromNonNullJson(CompressingUtils.readTextZipEntry(zip, "manifest.json"), CurseManifest.class);
String description = "No description";
try {
@@ -18,8 +18,8 @@
package org.jackhuang.hmcl.mod.mcbbs;
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.LaunchOptions;
import org.jackhuang.hmcl.mod.*;
@@ -70,7 +70,7 @@ public final class McbbsModpackProvider implements ModpackProvider {
}
@Override
public Modpack readManifest(ZipFile zip, Path file, Charset encoding) throws IOException, JsonParseException {
public Modpack readManifest(ZipArchiveReader zip, Path file, Charset encoding) throws IOException, JsonParseException {
ZipArchiveEntry mcbbsPackMeta = zip.getEntry("mcbbs.packmeta");
if (mcbbsPackMeta != null) {
return fromManifestFile(zip.getInputStream(mcbbsPackMeta), encoding);
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.mod.modrinth;
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
@@ -55,7 +55,7 @@ public final class ModrinthModpackProvider implements ModpackProvider {
}
@Override
public Modpack readManifest(ZipFile zip, Path file, Charset encoding) throws IOException, JsonParseException {
public Modpack readManifest(ZipArchiveReader zip, Path file, Charset encoding) throws IOException, JsonParseException {
ModrinthManifest manifest = JsonUtils.fromNonNullJson(CompressingUtils.readTextZipEntry(zip, "modrinth.index.json"), ModrinthManifest.class);
return new Modpack(manifest.getName(), "", manifest.getVersionId(), manifest.getGameVersion(), manifest.getSummary(), encoding, manifest) {
@Override
@@ -18,8 +18,8 @@
package org.jackhuang.hmcl.mod.multimc;
import com.google.gson.annotations.SerializedName;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -55,7 +55,7 @@ public final class MultiMCManifest {
* @throws IOException if zip file is malformed
* @throws com.google.gson.JsonParseException if manifest is malformed.
*/
public static MultiMCManifest readMultiMCModpackManifest(ZipFile zipFile, String rootEntryName) throws IOException {
public static MultiMCManifest readMultiMCModpackManifest(ZipArchiveReader zipFile, String rootEntryName) throws IOException {
ZipArchiveEntry mmcPack = zipFile.getEntry(rootEntryName + "mmc-pack.json");
if (mmcPack == null)
return null;
@@ -17,8 +17,8 @@
*/
package org.jackhuang.hmcl.mod.multimc;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
@@ -33,7 +33,6 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.stream.Stream;
public final class MultiMCModpackProvider implements ModpackProvider {
@@ -71,14 +70,12 @@ public final class MultiMCModpackProvider implements ModpackProvider {
}
}
private static String getRootEntryName(ZipFile file) throws IOException {
private static String getRootEntryName(ZipArchiveReader file) throws IOException {
final String instanceFileName = "instance.cfg";
if (file.getEntry(instanceFileName) != null) return "";
Enumeration<ZipArchiveEntry> entries = file.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
for (ZipArchiveEntry entry : file.getEntries()) {
String entryName = entry.getName();
int idx = entryName.indexOf('/');
@@ -92,7 +89,7 @@ public final class MultiMCModpackProvider implements ModpackProvider {
}
@Override
public Modpack readManifest(ZipFile modpackFile, Path modpackPath, Charset encoding) throws IOException {
public Modpack readManifest(ZipArchiveReader modpackFile, Path modpackPath, Charset encoding) throws IOException {
String rootEntryName = getRootEntryName(modpackFile);
MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(modpackFile, rootEntryName);
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.mod.server;
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
@@ -55,7 +55,7 @@ public final class ServerModpackProvider implements ModpackProvider {
}
@Override
public Modpack readManifest(ZipFile zip, Path file, Charset encoding) throws IOException, JsonParseException {
public Modpack readManifest(ZipArchiveReader zip, Path file, Charset encoding) throws IOException, JsonParseException {
String json = CompressingUtils.readTextZipEntry(zip, "server-manifest.json");
ServerModpackManifest manifest = JsonUtils.fromNonNullJson(json, ServerModpackManifest.class);
return manifest.toModpack(encoding);
@@ -17,8 +17,8 @@
*/
package org.jackhuang.hmcl.util.io;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
@@ -54,19 +54,16 @@ public final class CompressingUtils {
}
public static boolean testEncoding(Path zipFile, Charset encoding) throws IOException {
try (ZipFile zf = openZipFile(zipFile, encoding)) {
try (ZipArchiveReader zf = openZipFile(zipFile, encoding)) {
return testEncoding(zf, encoding);
}
}
public static boolean testEncoding(ZipFile zipFile, Charset encoding) throws IOException {
Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
public static boolean testEncoding(ZipArchiveReader zipFile, Charset encoding) throws IOException {
CharsetDecoder cd = newCharsetDecoder(encoding);
CharBuffer cb = CharBuffer.allocate(32);
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
for (ZipArchiveEntry entry : zipFile.getEntries()) {
if (entry.getGeneralPurposeBit().usesUTF8ForNames()) continue;
cd.reset();
@@ -88,12 +85,12 @@ public final class CompressingUtils {
}
public static Charset findSuitableEncoding(Path zipFile) throws IOException {
try (ZipFile zf = openZipFile(zipFile, StandardCharsets.UTF_8)) {
try (ZipArchiveReader zf = openZipFile(zipFile, StandardCharsets.UTF_8)) {
return findSuitableEncoding(zf);
}
}
public static Charset findSuitableEncoding(ZipFile zipFile) throws IOException {
public static Charset findSuitableEncoding(ZipArchiveReader zipFile) throws IOException {
if (testEncoding(zipFile, StandardCharsets.UTF_8)) return StandardCharsets.UTF_8;
if (OperatingSystem.NATIVE_CHARSET != StandardCharsets.UTF_8 && testEncoding(zipFile, OperatingSystem.NATIVE_CHARSET))
return OperatingSystem.NATIVE_CHARSET;
@@ -133,12 +130,12 @@ public final class CompressingUtils {
throw new IOException("Cannot find suitable encoding for the zip.");
}
public static ZipFile openZipFile(Path zipFile) throws IOException {
return new ZipFile(Files.newByteChannel(zipFile));
public static ZipArchiveReader openZipFile(Path zipFile) throws IOException {
return new ZipArchiveReader(Files.newByteChannel(zipFile));
}
public static ZipFile openZipFile(Path zipFile, Charset charset) throws IOException {
return new ZipFile(Files.newByteChannel(zipFile), charset.name());
public static ZipArchiveReader openZipFile(Path zipFile, Charset charset) throws IOException {
return new ZipArchiveReader(zipFile, charset);
}
public static final class Builder {
@@ -234,7 +231,7 @@ public final class CompressingUtils {
* @return the plain text content of given file.
*/
public static String readTextZipEntry(File zipFile, String name) throws IOException {
try (ZipFile s = new ZipFile(zipFile)) {
try (ZipArchiveReader s = new ZipArchiveReader(zipFile.toPath())) {
return readTextZipEntry(s, name);
}
}
@@ -247,7 +244,7 @@ public final class CompressingUtils {
* @throws IOException if the file is not a valid zip file.
* @return the plain text content of given file.
*/
public static String readTextZipEntry(ZipFile zipFile, String name) throws IOException {
public static String readTextZipEntry(ZipArchiveReader zipFile, String name) throws IOException {
return IOUtils.readFullyAsString(zipFile.getInputStream(zipFile.getEntry(name)));
}
@@ -260,7 +257,7 @@ public final class CompressingUtils {
* @return the plain text content of given file.
*/
public static String readTextZipEntry(Path zipFile, String name, Charset encoding) throws IOException {
try (ZipFile s = openZipFile(zipFile, encoding)) {
try (ZipArchiveReader s = openZipFile(zipFile, encoding)) {
return IOUtils.readFullyAsString(s.getInputStream(s.getEntry(name)));
}
}
@@ -17,8 +17,8 @@
*/
package org.jackhuang.hmcl.util.tree;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.ArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import java.io.Closeable;
import java.io.IOException;
@@ -39,7 +39,7 @@ public abstract class ArchiveFileTree<F, E extends ArchiveEntry> implements Clos
String name = namePath.toString();
if (name.endsWith(".jar") || name.endsWith(".zip")) {
return new ZipFileTree(new ZipFile(file));
return new ZipFileTree(new ZipArchiveReader(file));
} else if (name.endsWith(".tar") || name.endsWith(".tar.gz") || name.endsWith(".tgz")) {
return TarFileTree.open(file);
} else {
@@ -18,8 +18,8 @@
package org.jackhuang.hmcl.util.tree;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarFile;
import kala.compress.archivers.tar.TarArchiveEntry;
import kala.compress.archivers.tar.TarArchiveReader;
import org.jackhuang.hmcl.util.io.IOUtils;
import java.io.*;
@@ -31,19 +31,19 @@ import java.util.zip.GZIPInputStream;
/**
* @author Glavo
*/
public final class TarFileTree extends ArchiveFileTree<TarFile, TarArchiveEntry> {
public final class TarFileTree extends ArchiveFileTree<TarArchiveReader, TarArchiveEntry> {
public static TarFileTree open(Path file) throws IOException {
String fileName = file.getFileName().toString();
if (fileName.endsWith(".tar.gz") || fileName.endsWith(".tgz")) {
Path tempFile = Files.createTempFile("hmcl-", ".tar");
TarFile tarFile;
TarArchiveReader tarFile;
try (GZIPInputStream input = new GZIPInputStream(Files.newInputStream(file));
OutputStream output = Files.newOutputStream(tempFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)
) {
IOUtils.copyTo(input, output);
tarFile = new TarFile(tempFile.toFile());
tarFile = new TarArchiveReader(tempFile);
} catch (Throwable e) {
try {
Files.deleteIfExists(tempFile);
@@ -55,14 +55,14 @@ public final class TarFileTree extends ArchiveFileTree<TarFile, TarArchiveEntry>
return new TarFileTree(tarFile, tempFile);
} else {
return new TarFileTree(new TarFile(file), null);
return new TarFileTree(new TarArchiveReader(file), null);
}
}
private final Path tempFile;
private final Thread shutdownHook;
public TarFileTree(TarFile file, Path tempFile) throws IOException {
public TarFileTree(TarArchiveReader file, Path tempFile) throws IOException {
super(file);
this.tempFile = tempFile;
try {
@@ -17,23 +17,21 @@
*/
package org.jackhuang.hmcl.util.tree;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import kala.compress.archivers.zip.ZipArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
/**
* @author Glavo
*/
public final class ZipFileTree extends ArchiveFileTree<ZipFile, ZipArchiveEntry> {
public ZipFileTree(ZipFile file) throws IOException {
public final class ZipFileTree extends ArchiveFileTree<ZipArchiveReader, ZipArchiveEntry> {
public ZipFileTree(ZipArchiveReader file) throws IOException {
super(file);
try {
Enumeration<ZipArchiveEntry> entries = file.getEntries();
while (entries.hasMoreElements()) {
addEntry(entries.nextElement());
for (ZipArchiveEntry zipArchiveEntry : file.getEntries()) {
addEntry(zipArchiveEntry);
}
} catch (Throwable e) {
try {
@@ -1,56 +1,56 @@
{
"versions": [
{
"id": "1_19_deep_dark_experimental_snapshot-1",
"id": "1.19_deep_dark_experimental_snapshot-1",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_19_deep_dark_experimental_snapshot-1/1_19_deep_dark_experimental_snapshot-1.json",
"time": "2022-02-17T13:55:59+00:00",
"releaseTime": "2022-02-17T13:55:59+00:00"
},
{
"id": "1_18_experimental-snapshot-7",
"id": "1.18_experimental-snapshot-7",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-7/1_18_experimental-snapshot-7.json",
"time": "2021-09-08T12:33:23+00:00",
"releaseTime": "2021-09-08T12:33:23+00:00"
},
{
"id": "1_18_experimental-snapshot-6",
"id": "1.18_experimental-snapshot-6",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-6/1_18_experimental-snapshot-6.json",
"time": "2021-09-01T12:53:14+00:00",
"releaseTime": "2021-09-01T12:53:14+00:00"
},
{
"id": "1_18_experimental-snapshot-5",
"id": "1.18_experimental-snapshot-5",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-5/1_18_experimental-snapshot-5.json",
"time": "2021-08-25T14:41:57+00:00",
"releaseTime": "2021-08-25T14:41:57+00:00"
},
{
"id": "1_18_experimental-snapshot-4",
"id": "1.18_experimental-snapshot-4",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-4/1_18_experimental-snapshot-4.json",
"time": "2021-08-17T16:10:25+00:00",
"releaseTime": "2021-08-17T16:10:25+00:00"
},
{
"id": "1_18_experimental-snapshot-3",
"id": "1.18_experimental-snapshot-3",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-3/1_18_experimental-snapshot-3.json",
"time": "2021-08-10T12:42:45+00:00",
"releaseTime": "2021-08-10T12:42:45+00:00"
},
{
"id": "1_18_experimental-snapshot-2",
"id": "1.18_experimental-snapshot-2",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-2/1_18_experimental-snapshot-2.json",
"time": "2021-07-20T13:35:08+00:00",
"releaseTime": "2021-07-20T13:35:08+00:00"
},
{
"id": "1_18_experimental-snapshot-1",
"id": "1.18_experimental-snapshot-1",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_18_experimental-snapshot-1/1_18_experimental-snapshot-1.json",
"time": "2021-07-13T12:54:19+00:00",
@@ -120,43 +120,43 @@
"releaseTime": "2019-11-29T15:41:39+00:00"
},
{
"id": "1_14_combat-3",
"id": "1.14_combat-3",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_14_combat-3/1_14_combat-3.json",
"time": "2019-10-31T14:31:38+00:00",
"releaseTime": "2019-10-31T14:31:38+00:00"
},
{
"id": "1_14_combat-0",
"id": "1.14_combat-0",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_14_combat-0/1_14_combat-0.json",
"time": "2019-08-13T07:33:42+00:00",
"releaseTime": "2019-08-13T07:33:42+00:00"
},
{
"id": "1_14_combat-212796",
"id": "1.14_combat-212796",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_14_combat-212796/1_14_combat-212796.json",
"time": "2019-06-20T13:23:44+00:00",
"releaseTime": "2019-06-20T13:23:44+00:00"
},
{
"id": "2point0_blue",
"type": "release",
"id": "2.0_blue",
"type": "snapshot",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/2point0_blue/2point0_blue.json",
"time": "2013-08-06T06:00:02-05:00",
"releaseTime": "2013-03-20T05:00:02-05:00"
},
{
"id": "2point0_red",
"type": "release",
"id": "2.0_red",
"type": "snapshot",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/2point0_red/2point0_red.json",
"time": "2013-08-06T06:00:01-05:00",
"releaseTime": "2013-03-20T05:00:01-05:00"
},
{
"id": "2point0_purple",
"type": "release",
"id": "2.0_purple",
"type": "snapshot",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/2point0_purple/2point0_purple.json",
"time": "2013-08-06T06:00:00-05:00",
"releaseTime": "2013-03-20T05:00:00-05:00"
@@ -632,70 +632,70 @@
},
{
"id": "1.0.0-rc2-3",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1.0.0-rc2-3/1.0.0-rc2-3.json",
"time": "2013-08-06T07:33:30-07:00",
"releaseTime": "2011-11-14T15:00:00-07:00"
},
{
"id": "1.0.0-rc2-2",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1.0.0-rc2-2/1.0.0-rc2-2.json",
"time": "2019-04-21T21:04:24+00:00",
"releaseTime": "2011-11-14T00:00:00+00:00"
},
{
"id": "1.0.0-rc2-1",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1.0.0-rc2-1/1.0.0-rc2-1.json",
"time": "2019-11-21T22:37:45+00:00",
"releaseTime": "2011-11-13T17:33:00+00:00"
},
{
"id": "1.0.0-rc1",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1.0.0-rc1/1.0.0-rc1.json",
"time": "2013-08-06T07:33:30-07:00",
"releaseTime": "2011-11-13T15:00:00-07:00"
},
{
"id": "b1.9-pre6",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre6/b1_9-pre6.json",
"time": "2011-11-11T00:00:00+00:00",
"releaseTime": "2011-11-11T00:00:00+00:00"
},
{
"id": "b1.9-pre5",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre5/b1_9-pre5.json",
"time": "2011-10-27T00:00:00+00:00",
"releaseTime": "2011-10-27T00:00:00+00:00"
},
{
"id": "b1.9-pre4",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre4/b1_9-pre4.json",
"time": "2011-10-13T00:00:00+00:00",
"releaseTime": "2011-10-13T00:00:00+00:00"
},
{
"id": "b1.9-pre3",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre3/b1_9-pre3.json",
"time": "2011-10-06T00:00:00+00:00",
"releaseTime": "2011-10-06T00:00:00+00:00"
},
{
"id": "b1.9-pre2",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre2/b1_9-pre2.json",
"time": "2011-09-29T00:00:00+00:00",
"releaseTime": "2011-09-29T00:00:00+00:00"
},
{
"id": "b1.9-pre1",
"type": "snapshot",
"type": "old_beta",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/b1_9-pre1/b1_9-pre1.json",
"time": "2011-09-22T00:00:00+00:00",
"releaseTime": "2011-09-22T00:00:00+00:00"
@@ -1359,4 +1359,4 @@
"releaseTime": "2009-05-20T00:00:00-00:00"
}
]
}
}