From c497f71804b9f94cd36417998c857f9b3067ba86 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 22 Jul 2023 20:18:53 +0800 Subject: [PATCH 001/104] Support #2376 --- .../java/org/jackhuang/hmcl/Launcher.java | 2 +- .../hmcl/ui/construct/TwoLineListItem.java | 1 + .../hmcl/ui/versions/DownloadPage.java | 188 +++++++++--------- .../resources/assets/lang/I18N_zh.properties | 4 +- .../assets/lang/I18N_zh_CN.properties | 2 +- .../jackhuang/hmcl/mod/curse/CurseAddon.java | 2 +- 6 files changed, 98 insertions(+), 101 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java index 89169b8a33..400859f7ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java @@ -96,7 +96,7 @@ public void start(Stage primaryStage) { } }); - RemoteMod.registerEmptyRemoteMod(new RemoteMod("", "", i18n("mods.broken_dependency.title"), i18n("mods.broken_dependency.desc"), new ArrayList<>(), "", "/assets/img/icon.png", new RemoteMod.IMod() { + RemoteMod.registerEmptyRemoteMod(new RemoteMod("", "", i18n("mods.broken_dependency.title"), i18n("mods.broken_dependency.desc"), new ArrayList<>(), "", "/assets/img/icon@8x.png", new RemoteMod.IMod() { @Override public List loadDependencies(RemoteModRepository modRepository) throws IOException { throw new IOException(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 51c98e1596..a724e96399 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -62,6 +62,7 @@ public TwoLineListItem() { Label tagLabel = new Label(); tagLabel.getStyleClass().add("tag"); tagLabel.setText(tag); + FXUtils.installFastTooltip(tagLabel, tag); HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); return tagLabel; }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index e20b06a329..0153c0e012 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -19,7 +19,6 @@ import com.jfoenix.controls.JFXButton; import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; @@ -27,10 +26,7 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.control.Control; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.Skin; -import javafx.scene.control.SkinBase; +import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.*; @@ -81,7 +77,6 @@ public class DownloadPage extends Control implements DecoratorPage { private final DownloadCallback callback; private final DownloadListPage page; - private List dependencies; private SimpleMultimap versions; public DownloadPage(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) { @@ -105,10 +100,8 @@ private void loadModVersions() { setLoading(true); setFailed(false); - Task.allOf( - Task.supplyAsync(() -> addon.getData().loadDependencies(repository)), - Task.supplyAsync(() -> { - Stream versions = addon.getData().loadVersions(repository); + Task.supplyAsync(() -> { + Stream versions = addon.getData().loadVersions(repository); // if (StringUtils.isNotBlank(version.getVersion())) { // Optional gameVersion = GameVersion.minecraftVersion(versionJar); // if (gameVersion.isPresent()) { @@ -116,25 +109,18 @@ private void loadModVersions() { // .filter(file -> file.getGameVersions().contains(gameVersion.get()))); // } // } - return sortVersions(versions); - })) - .whenComplete(Schedulers.javafx(), (result, exception) -> { - if (exception == null) { - @SuppressWarnings("unchecked") - List dependencies = (List) result.get(0); - @SuppressWarnings("unchecked") - SimpleMultimap versions = (SimpleMultimap) result.get(1); - - this.dependencies = dependencies; - this.versions = versions; - - loaded.set(true); - setFailed(false); - } else { - setFailed(true); - } - setLoading(false); - }).start(); + return sortVersions(versions); + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + this.versions = result; + + loaded.set(true); + setFailed(false); + } else { + setFailed(true); + } + setLoading(false); + }).start(); } private SimpleMultimap sortVersions(Stream versions) { @@ -280,29 +266,6 @@ protected ModDownloadPageSkin(DownloadPage control) { runInFX(() -> FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage())); } - { - ComponentList dependencyPane = new ComponentList(); - dependencyPane.getStyleClass().add("no-padding"); - - FXUtils.onChangeAndOperate(control.loaded, loaded -> { - if (loaded) { - dependencyPane.getContent().setAll(control.dependencies.stream() - .map(dependency -> new DependencyModItem(getSkinnable().page, dependency, control.version, control.callback)) - .collect(Collectors.toList())); - } - }); - - Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies")); - - BooleanBinding show = Bindings.createBooleanBinding(() -> control.loaded.get() && !control.dependencies.isEmpty(), control.loaded); - title.managedProperty().bind(show); - title.visibleProperty().bind(show); - dependencyPane.managedProperty().bind(show); - dependencyPane.visibleProperty().bind(show); - - pane.getChildren().addAll(title, dependencyPane); - } - SpinnerPane spinnerPane = new SpinnerPane(); VBox.setVgrow(spinnerPane, Priority.ALWAYS); pane.getChildren().add(spinnerPane); @@ -374,59 +337,92 @@ private static final class DependencyModItem extends StackPane { private static final class ModItem extends StackPane { ModItem(RemoteMod.Version dataItem, DownloadPage selfPage) { - HBox pane = new HBox(8); - pane.setPadding(new Insets(8)); - pane.setAlignment(Pos.CENTER_LEFT); - TwoLineListItem content = new TwoLineListItem(); - StackPane graphicPane = new StackPane(); - JFXButton saveAsButton = new JFXButton(); - - RipplerContainer container = new RipplerContainer(pane); - container.setOnMouseClicked(e -> selfPage.download(dataItem)); - getChildren().setAll(container); + VBox pane = new VBox(8); - saveAsButton.getStyleClass().add("toggle-icon4"); - saveAsButton.setGraphic(SVG.contentSaveMoveOutline(Theme.blackFillBinding(), -1, -1)); + { + HBox descPane = new HBox(8); + descPane.setPadding(new Insets(8)); + descPane.setAlignment(Pos.CENTER_LEFT); - HBox.setHgrow(content, Priority.ALWAYS); - pane.getChildren().setAll(graphicPane, content, saveAsButton); + { + Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies")); + pane.getChildren().add(title); + } - content.setTitle(dataItem.getName()); - content.setSubtitle(FORMATTER.format(dataItem.getDatePublished().toInstant())); - saveAsButton.setOnAction(e -> selfPage.saveAs(dataItem)); + { + VBox dependencies = new VBox(); + Task.supplyAsync(() -> { + List dependencyModItems = new ArrayList<>(); + for (String dependencyID : dataItem.getDependencies()) { + dependencyModItems.add(new DependencyModItem( + selfPage.page, + StringUtils.isNotBlank(dependencyID) ? dataItem.getSelf().getType().getRemoteModRepository().getModById(dependencyID) : RemoteMod.getEmptyRemoteMod(), + selfPage.version, + selfPage.callback + )); + } + return dependencyModItems; + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + dependencies.getChildren().addAll(result); + } else { + dependencies.getChildren().add(new Label(i18n("download.failed.refresh"))); + } + }).start(); + pane.getChildren().add(dependencies); + } - switch (dataItem.getVersionType()) { - case Release: + { + StackPane graphicPane = new StackPane(); graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24)); - content.getTags().add(i18n("version.game.release")); - break; - case Beta: - graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24)); - content.getTags().add(i18n("version.game.snapshot")); - break; - case Alpha: - graphicPane.getChildren().setAll(SVG.alphaCircleOutline(Theme.blackFillBinding(), 24, 24)); - content.getTags().add(i18n("version.game.snapshot")); - break; - } - for (ModLoaderType modLoaderType : dataItem.getLoaders()) { - switch (modLoaderType) { - case FORGE: - content.getTags().add(i18n("install.installer.forge")); - break; - case FABRIC: - content.getTags().add(i18n("install.installer.fabric")); - break; - case LITE_LOADER: - content.getTags().add(i18n("install.installer.liteloader")); - break; - case QUILT: - content.getTags().add(i18n("install.installer.quilt")); - break; + TwoLineListItem content = new TwoLineListItem(); + HBox.setHgrow(content, Priority.ALWAYS); + content.setTitle(dataItem.getName()); + content.setSubtitle(FORMATTER.format(dataItem.getDatePublished().toInstant())); + + switch (dataItem.getVersionType()) { + case Alpha: + case Beta: + content.getTags().add(i18n("version.game.snapshot")); + break; + case Release: + content.getTags().add(i18n("version.game.release")); + break; + } + + for (ModLoaderType modLoaderType : dataItem.getLoaders()) { + switch (modLoaderType) { + case FORGE: + content.getTags().add(i18n("install.installer.forge")); + break; + case FABRIC: + content.getTags().add(i18n("install.installer.fabric")); + break; + case LITE_LOADER: + content.getTags().add(i18n("install.installer.liteloader")); + break; + case QUILT: + content.getTags().add(i18n("install.installer.quilt")); + break; + } + } + + JFXButton saveAsButton = new JFXButton(); + saveAsButton.getStyleClass().add("toggle-icon4"); + saveAsButton.setGraphic(SVG.contentSaveMoveOutline(Theme.blackFillBinding(), -1, -1)); + saveAsButton.setOnAction(e -> selfPage.saveAs(dataItem)); + + descPane.getChildren().setAll(graphicPane, content, saveAsButton); } + + pane.getChildren().add(descPane); } + RipplerContainer container = new RipplerContainer(pane); + container.setOnMouseClicked(e -> selfPage.download(dataItem)); + getChildren().setAll(container); + // Workaround for https://github.com/huanghongxun/HMCL/issues/2129 this.setMinHeight(50); } diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 6fc9212640..d5a388fcce 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -728,8 +728,8 @@ mods=模組 mods.add=新增模組 mods.add.failed=新增模組 %s 失敗。 mods.add.success=成功新增模組 %s。 -mods.broken_dependency.title=損壞前置模組 -mods.broken_dependency.desc=該前置模組曾經在該模組倉庫上存在過,但現在被刪除了。換個下載源試試吧。 +mods.broken_dependency.title=損壞的前置模組 +mods.broken_dependency.desc=該前置模組曾經在該模組倉庫上存在過,但現在被刪除了,換個下載源試試吧。 mods.category=類別 mods.check_updates=檢查模組更新 mods.check_updates.current_version=當前版本 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 4073a113d5..d65ce2a476 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -728,7 +728,7 @@ mods=模组 mods.add=添加模组 mods.add.failed=添加模组 %s 失败。 mods.add.success=成功添加模组 %s。 -mods.broken_dependency.title=损坏前置模组 +mods.broken_dependency.title=损坏的前置模组 mods.broken_dependency.desc=该前置模组曾经在该模组仓库上存在过,但现在被删除了。换个下载源试试吧。 mods.category=类别 mods.check_updates=检查模组更新 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java index 5b8fbae6c3..913cb4aa4c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java @@ -566,7 +566,7 @@ public RemoteMod.Version toVersion() { getFileDate(), versionType, new RemoteMod.File(Collections.emptyMap(), getDownloadUrl(), getFileName()), - Collections.emptyList(), + dependencies.stream().map(dependency -> Integer.toString(dependency.modId)).collect(Collectors.toList()), gameVersions.stream().filter(ver -> ver.startsWith("1.") || ver.contains("w")).collect(Collectors.toList()), gameVersions.stream().flatMap(version -> { if ("fabric".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FABRIC); From 8c453beec9cee4e371b80a7efb38173b50c29992 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 22 Jul 2023 22:00:40 +0800 Subject: [PATCH 002/104] Add necessary @Nullable annotations --- .../src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java index 87ce46bc76..7eca7a5616 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java @@ -20,6 +20,7 @@ import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.Date; @@ -135,11 +136,11 @@ public static class Version { private final Date datePublished; private final VersionType versionType; private final File file; - private final List dependencies; + private final List<@Nullable String> dependencies; private final List gameVersions; private final List loaders; - public Version(IVersion self, String modid, String name, String version, String changelog, Date datePublished, VersionType versionType, File file, List dependencies, List gameVersions, List loaders) { + public Version(IVersion self, String modid, String name, String version, String changelog, Date datePublished, VersionType versionType, File file, List<@Nullable String> dependencies, List gameVersions, List loaders) { this.self = self; this.modid = modid; this.name = name; @@ -185,7 +186,7 @@ public File getFile() { return file; } - public List getDependencies() { + public List<@Nullable String> getDependencies() { return dependencies; } From face066614368b3356f814b96a2dbc50764f46e0 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 28 Jul 2023 11:59:31 +0800 Subject: [PATCH 003/104] Display different types of dependencies in different sections. --- .../hmcl/ui/construct/ComponentList.java | 28 ++++++- .../hmcl/ui/construct/ComponentListCell.java | 18 +++-- .../hmcl/ui/versions/DownloadPage.java | 73 ++++++++++++------- .../hmcl/ui/versions/ModListPageSkin.java | 17 ++--- .../org/jackhuang/hmcl/mod/RemoteMod.java | 73 ++++++++++++++++++- .../jackhuang/hmcl/mod/curse/CurseAddon.java | 18 ++++- .../modrinth/ModrinthRemoteModRepository.java | 41 ++++++----- 7 files changed, 199 insertions(+), 69 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java index 03dd49b875..bb47fe0e85 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.ui.construct; +import javafx.application.Platform; import javafx.beans.DefaultProperty; import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; @@ -36,11 +37,15 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.javafx.MappedObservableList; import java.util.List; import java.util.function.Supplier; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + @DefaultProperty("content") public class ComponentList extends Control { private final StringProperty title = new SimpleStringProperty(this, "title", "Group"); @@ -49,6 +54,7 @@ public class ComponentList extends Control { private boolean hasSubtitle = false; public final ObservableList content = FXCollections.observableArrayList(); private Supplier> lazyInitializer; + private SpinnerPane spinner; public ComponentList() { getStyleClass().add("options-list"); @@ -57,6 +63,10 @@ public ComponentList() { public ComponentList(Supplier> lazyInitializer) { this(); this.lazyInitializer = lazyInitializer; + spinner = new SpinnerPane(); + spinner.getStyleClass().add("small-spinner-pane"); + spinner.showSpinner(); + this.getContent().setAll(spinner); } public String getTitle() { @@ -107,12 +117,24 @@ public ObservableList getContent() { return content; } - void doLazyInit() { + void doLazyInit(Runnable callback) { if (lazyInitializer != null) { - this.getContent().setAll(lazyInitializer.get()); setNeedsLayout(true); - lazyInitializer = null; + + Task.supplyAsync(lazyInitializer::get) + .whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + spinner.hideSpinner(); + this.getContent().setAll(result); + setNeedsLayout(true); + lazyInitializer = null; + } else { + spinner.setLoading(false); + } + Platform.runLater(callback); + }).start(); } + callback.run(); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java index ed9dc7314d..28b95cd8b6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -36,6 +36,7 @@ import javafx.scene.shape.Rectangle; import javafx.util.Duration; import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.AnimationUtils; @@ -146,12 +147,8 @@ private void updateLayout() { boolean expanded = !isExpanded(); setExpanded(expanded); - if (expanded) { - list.doLazyInit(); - list.layout(); - } - Platform.runLater(() -> { + Runnable callback = () -> { double newAnimatedHeight = (list.prefHeight(-1) + 8 + 10) * (expanded ? 1 : -1); double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(-1); double contentHeight = expanded ? newAnimatedHeight : 0; @@ -179,7 +176,16 @@ private void updateLayout() { updateClip(newHeight); } } - }); + }; + + if (expanded) { + list.doLazyInit(() -> { + list.layout(); + callback.run(); + }); + } else { + Platform.runLater(callback); + } }; headerRippler.setOnMouseClicked(onExpand); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 0153c0e012..c1571d6b93 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -45,6 +45,7 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.SimpleMultimap; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; @@ -54,13 +55,16 @@ import org.jetbrains.annotations.Nullable; import java.io.File; +import java.io.IOException; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.*; +import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; @@ -266,6 +270,24 @@ protected ModDownloadPageSkin(DownloadPage control) { runInFX(() -> FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage())); } + if (control.repository.getType() != RemoteModRepository.Type.MOD) { + Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies.embedded")); + ComponentList dependencyPane = new ComponentList(Lang::immutableListOf); + Task.supplyAsync(() -> control.addon.getData().loadDependencies(control.repository).stream() + .map(remoteMod -> new DependencyModItem(getSkinnable().page, remoteMod, control.version, control.callback)) + .collect(Collectors.toList()) + ).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + dependencyPane.getContent().setAll(result); + } else { + dependencyPane.getContent().setAll(new Label(i18n("download.failed.refresh"))); + } + }).start(); + dependencyPane.getStyleClass().add("no-padding"); + + pane.getChildren().addAll(title, dependencyPane); + } + SpinnerPane spinnerPane = new SpinnerPane(); VBox.setVgrow(spinnerPane, Priority.ALWAYS); pane.getChildren().add(spinnerPane); @@ -344,32 +366,31 @@ private static final class ModItem extends StackPane { descPane.setPadding(new Insets(8)); descPane.setAlignment(Pos.CENTER_LEFT); - { - Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies")); - pane.getChildren().add(title); - } - - { - VBox dependencies = new VBox(); - Task.supplyAsync(() -> { - List dependencyModItems = new ArrayList<>(); - for (String dependencyID : dataItem.getDependencies()) { - dependencyModItems.add(new DependencyModItem( - selfPage.page, - StringUtils.isNotBlank(dependencyID) ? dataItem.getSelf().getType().getRemoteModRepository().getModById(dependencyID) : RemoteMod.getEmptyRemoteMod(), - selfPage.version, - selfPage.callback - )); - } - return dependencyModItems; - }).whenComplete(Schedulers.javafx(), (result, exception) -> { - if (exception == null) { - dependencies.getChildren().addAll(result); - } else { - dependencies.getChildren().add(new Label(i18n("download.failed.refresh"))); + if (selfPage.repository.getType() == RemoteModRepository.Type.MOD) { + VBox dependenciesBox = new VBox(); + EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); + try { + for (RemoteMod.Dependency dependency : dataItem.getDependencies()) { + if (dependency.getType() == RemoteMod.DependencyType.INCOMPATIBLE || dependency.getType() == RemoteMod.DependencyType.BROKEN) { + continue; + } + + if (!dependencies.containsKey(dependency.getType())) { + dependencies.put(dependency.getType(), new ArrayList<>()); + } + dependencies.get(dependency.getType()).add(new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback)); } - }).start(); - pane.getChildren().add(dependencies); + } catch (IOException exception) { + dependencies.clear(); + dependenciesBox.getChildren().setAll(new Label(i18n("download.failed.refresh"))); + LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid())); + } + + for (Map.Entry> entry : dependencies.entrySet()) { + dependenciesBox.getChildren().add(new Label(i18n("mods.dependency." + entry.getKey().name().toLowerCase()))); // TODO + dependenciesBox.getChildren().addAll(entry.getValue()); + } + pane.getChildren().add(dependenciesBox); } { @@ -414,13 +435,13 @@ private static final class ModItem extends StackPane { saveAsButton.setOnAction(e -> selfPage.saveAs(dataItem)); descPane.getChildren().setAll(graphicPane, content, saveAsButton); + descPane.setOnMouseClicked(e -> selfPage.download(dataItem)); } pane.getChildren().add(descPane); } RipplerContainer container = new RipplerContainer(pane); - container.setOnMouseClicked(e -> selfPage.download(dataItem)); getChildren().setAll(container); // Workaround for https://github.com/huanghongxun/HMCL/issues/2129 diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index af461b1057..70f6f1e510 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -320,25 +320,18 @@ class ModInfoDialog extends JFXDialogLayout { setBody(description); if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) { - Lang.>>>immutableListOf( - pair("mods.curseforge", pair( - CurseForgeRemoteModRepository.MODS, - (remoteVersion) -> Integer.toString(((CurseAddon.LatestFile) remoteVersion.getSelf()).getModId()) - )), - pair("mods.modrinth", pair( - ModrinthRemoteModRepository.MODS, - (remoteVersion) -> ((ModrinthRemoteModRepository.ProjectVersion) remoteVersion.getSelf()).getProjectId() - )) + Lang.>immutableListOf( + pair("mods.curseforge", CurseForgeRemoteModRepository.MODS), + pair("mods.modrinth", ModrinthRemoteModRepository.MODS) ).forEach(item -> { String text = item.getKey(); - RemoteModRepository remoteModRepository = item.getValue().getKey(); - Function projectIDProvider = item.getValue().getValue(); + RemoteModRepository remoteModRepository = item.getValue(); JFXHyperlink button = new JFXHyperlink(i18n(text)); Task.runAsync(() -> { Optional versionOptional = remoteModRepository.getRemoteVersionByLocalFile(modInfo.getModInfo(), modInfo.getModInfo().getFile()); if (versionOptional.isPresent()) { - RemoteMod remoteMod = remoteModRepository.getModById(projectIDProvider.apply(versionOptional.get())); + RemoteMod remoteMod = remoteModRepository.getModById(versionOptional.get().getModid()); FXUtils.runInFX(() -> { button.setOnAction(e -> { fireEvent(new DialogCloseEvent()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java index 7eca7a5616..33c6d55aee 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java @@ -20,7 +20,6 @@ import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.Date; @@ -102,6 +101,72 @@ public enum VersionType { Alpha } + public enum DependencyType { + EMBEDDED, + OPTIONAL, + REQUIRED, + TOOL, + INCLUDE, + INCOMPATIBLE, + BROKEN + } + + public static class Dependency { + private static Dependency BROKEN_DEPENDENCY = null; + + private final DependencyType type; + + private final RemoteModRepository remoteModRepository; + + private final String id; + + private RemoteMod remoteMod = null; + + private Dependency(DependencyType type, RemoteModRepository remoteModRepository, String modid) { + this.type = type; + this.remoteModRepository = remoteModRepository; + this.id = modid; + } + + public static Dependency ofGeneral(DependencyType type, RemoteModRepository remoteModRepository, String modid) { + if (type == DependencyType.BROKEN) { + return ofBroken(); + } else { + return new Dependency(type, remoteModRepository, modid); + } + } + + public static Dependency ofBroken() { + if (BROKEN_DEPENDENCY == null) { + BROKEN_DEPENDENCY = new Dependency(DependencyType.BROKEN, null, null); + } + return BROKEN_DEPENDENCY; + } + + public DependencyType getType() { + return this.type; + } + + public RemoteModRepository getRemoteModRepository() { + return this.remoteModRepository; + } + + public String getId() { + return this.id; + } + + public RemoteMod load() throws IOException { + if (this.remoteMod == null) { + if (this.type == DependencyType.BROKEN) { + this.remoteMod = RemoteMod.getEmptyRemoteMod(); + } else { + this.remoteMod = this.remoteModRepository.getModById(this.id); + } + } + return this.remoteMod; + } + } + public enum Type { CURSEFORGE(CurseForgeRemoteModRepository.MODS), MODRINTH(ModrinthRemoteModRepository.MODS); @@ -136,11 +201,11 @@ public static class Version { private final Date datePublished; private final VersionType versionType; private final File file; - private final List<@Nullable String> dependencies; + private final List dependencies; private final List gameVersions; private final List loaders; - public Version(IVersion self, String modid, String name, String version, String changelog, Date datePublished, VersionType versionType, File file, List<@Nullable String> dependencies, List gameVersions, List loaders) { + public Version(IVersion self, String modid, String name, String version, String changelog, Date datePublished, VersionType versionType, File file, List dependencies, List gameVersions, List loaders) { this.self = self; this.modid = modid; this.name = name; @@ -186,7 +251,7 @@ public File getFile() { return file; } - public List<@Nullable String> getDependencies() { + public List getDependencies() { return dependencies; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java index 913cb4aa4c..5f714e73a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java @@ -21,6 +21,8 @@ import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Pair; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -30,6 +32,15 @@ @Immutable public class CurseAddon implements RemoteMod.IMod { + public static final Map RELATION_TYPE = Lang.mapOf( + Pair.pair(1, RemoteMod.DependencyType.EMBEDDED), + Pair.pair(2, RemoteMod.DependencyType.OPTIONAL), + Pair.pair(3, RemoteMod.DependencyType.REQUIRED), + Pair.pair(4, RemoteMod.DependencyType.TOOL), + Pair.pair(5, RemoteMod.DependencyType.INCOMPATIBLE), + Pair.pair(6, RemoteMod.DependencyType.INCLUDE) + ); + private final int id; private final int gameId; private final String name; @@ -566,7 +577,12 @@ public RemoteMod.Version toVersion() { getFileDate(), versionType, new RemoteMod.File(Collections.emptyMap(), getDownloadUrl(), getFileName()), - dependencies.stream().map(dependency -> Integer.toString(dependency.modId)).collect(Collectors.toList()), + dependencies.stream().map(dependency -> { + if (!RELATION_TYPE.containsKey(dependency.getRelationType())) { + throw new IllegalStateException("Broken datas."); + } + return RemoteMod.Dependency.ofGeneral(RELATION_TYPE.get(dependency.getRelationType()), CurseForgeRemoteModRepository.MODS, Integer.toString(dependency.getModId())); + }).filter(Objects::nonNull).collect(Collectors.toList()), gameVersions.stream().filter(ver -> ver.startsWith("1.") || ver.contains("w")).collect(Collectors.toList()), gameVersions.stream().flatMap(version -> { if ("fabric".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FABRIC); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index f367489e29..b0e8737f5f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -286,17 +286,12 @@ public List getVersions() { @Override public List loadDependencies(RemoteModRepository modRepository) throws IOException { - Set dependencies = modRepository.getRemoteVersionsById(getId()) + Set dependencies = modRepository.getRemoteVersionsById(getId()) .flatMap(version -> version.getDependencies().stream()) .collect(Collectors.toSet()); List mods = new ArrayList<>(); - for (String dependencyId : dependencies) { - if (dependencyId == null) { - mods.add(RemoteMod.getEmptyRemoteMod()); - } - if (StringUtils.isNotBlank(dependencyId)) { - mods.add(modRepository.getModById(dependencyId)); - } + for (RemoteMod.Dependency dependency : dependencies) { + mods.add(dependency.load()); } return mods; } @@ -351,6 +346,13 @@ public String getDependencyType() { } public static class ProjectVersion implements RemoteMod.IVersion { + private static final Map DEPENDENCY_TYPE = Lang.mapOf( + Pair.pair("required", RemoteMod.DependencyType.REQUIRED), + Pair.pair("optional", RemoteMod.DependencyType.OPTIONAL), + Pair.pair("embedded", RemoteMod.DependencyType.EMBEDDED), + Pair.pair("incompatible", RemoteMod.DependencyType.INCOMPATIBLE) + ); + private final String name; @SerializedName("version_number") @@ -496,7 +498,17 @@ public Optional toVersion() { datePublished, type, files.get(0).toFile(), - dependencies.stream().map(dependency -> dependency.getVersionId() == null ? null : dependency.getProjectId()).collect(Collectors.toList()), + dependencies.stream().map(dependency -> { + if (dependency.projectId == null) { + return RemoteMod.Dependency.ofBroken(); + } + + if (!DEPENDENCY_TYPE.containsKey(dependency.dependencyType)) { + throw new IllegalStateException("Broken datas"); + } + + return RemoteMod.Dependency.ofGeneral(DEPENDENCY_TYPE.get(dependency.dependencyType), MODS, dependency.projectId); + }).filter(Objects::nonNull).collect(Collectors.toList()), gameVersions, loaders.stream().flatMap(loader -> { if ("fabric".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FABRIC); @@ -651,17 +663,12 @@ public String getLatestVersion() { @Override public List loadDependencies(RemoteModRepository modRepository) throws IOException { - Set dependencies = modRepository.getRemoteVersionsById(getProjectId()) + Set dependencies = modRepository.getRemoteVersionsById(getProjectId()) .flatMap(version -> version.getDependencies().stream()) .collect(Collectors.toSet()); List mods = new ArrayList<>(); - for (String dependencyId : dependencies) { - if (dependencyId == null) { - mods.add(RemoteMod.getEmptyRemoteMod()); - } - if (StringUtils.isNotBlank(dependencyId)) { - mods.add(modRepository.getModById(dependencyId)); - } + for (RemoteMod.Dependency dependency : dependencies) { + mods.add(dependency.load()); } return mods; } From 8928f2975887cbaa715e868605793db5259abf44 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 28 Jul 2023 12:54:33 +0800 Subject: [PATCH 004/104] Fix checkstyle --- .../java/org/jackhuang/hmcl/ui/construct/ComponentList.java | 2 -- .../org/jackhuang/hmcl/ui/construct/ComponentListCell.java | 6 ++++-- .../org/jackhuang/hmcl/ui/versions/ModListPageSkin.java | 2 -- .../src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java index bb47fe0e85..682fb24e9a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java @@ -44,8 +44,6 @@ import java.util.List; import java.util.function.Supplier; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - @DefaultProperty("content") public class ComponentList extends Control { private final StringProperty title = new SimpleStringProperty(this, "title", "Group"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java index 28b95cd8b6..984e89a092 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -32,11 +32,13 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; -import javafx.scene.layout.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; import javafx.util.Duration; import org.jackhuang.hmcl.setting.Theme; -import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.AnimationUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 70f6f1e510..4cccb6dd4a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -36,7 +36,6 @@ import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; -import org.jackhuang.hmcl.mod.curse.CurseAddon; import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; import org.jackhuang.hmcl.setting.Profile; @@ -63,7 +62,6 @@ import java.nio.file.Path; import java.util.Locale; import java.util.Optional; -import java.util.function.Function; import java.util.function.Predicate; import java.util.logging.Level; import java.util.regex.Pattern; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java index 33c6d55aee..db71908648 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java @@ -111,7 +111,7 @@ public enum DependencyType { BROKEN } - public static class Dependency { + public static final class Dependency { private static Dependency BROKEN_DEPENDENCY = null; private final DependencyType type; From 1ba62a69226ef6071b0a858c017b517bdb28c3c6 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 28 Jul 2023 17:34:04 +0800 Subject: [PATCH 005/104] Add I18N for different types of dependencies. --- .../java/org/jackhuang/hmcl/Launcher.java | 76 ++++++++++--------- .../hmcl/ui/versions/DownloadPage.java | 26 +++++-- .../resources/assets/lang/I18N.properties | 8 +- .../resources/assets/lang/I18N_es.properties | 1 - .../resources/assets/lang/I18N_ja.properties | 1 - .../resources/assets/lang/I18N_ru.properties | 1 - .../resources/assets/lang/I18N_zh.properties | 8 +- .../assets/lang/I18N_zh_CN.properties | 8 +- 8 files changed, 80 insertions(+), 49 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java index 400859f7ca..345528b816 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java @@ -71,42 +71,7 @@ public void start(Stage primaryStage) { CookieHandler.setDefault(COOKIE_MANAGER); - Skin.registerDefaultSkinLoader((type) -> { - switch (type) { - case ALEX: - return Skin.class.getResourceAsStream("/assets/img/skin/alex.png"); - case ARI: - return Skin.class.getResourceAsStream("/assets/img/skin/ari.png"); - case EFE: - return Skin.class.getResourceAsStream("/assets/img/skin/efe.png"); - case KAI: - return Skin.class.getResourceAsStream("/assets/img/skin/kai.png"); - case MAKENA: - return Skin.class.getResourceAsStream("/assets/img/skin/makena.png"); - case NOOR: - return Skin.class.getResourceAsStream("/assets/img/skin/noor.png"); - case STEVE: - return Skin.class.getResourceAsStream("/assets/img/skin/steve.png"); - case SUNNY: - return Skin.class.getResourceAsStream("/assets/img/skin/sunny.png"); - case ZURI: - return Skin.class.getResourceAsStream("/assets/img/skin/zuri.png"); - default: - return null; - } - }); - - RemoteMod.registerEmptyRemoteMod(new RemoteMod("", "", i18n("mods.broken_dependency.title"), i18n("mods.broken_dependency.desc"), new ArrayList<>(), "", "/assets/img/icon@8x.png", new RemoteMod.IMod() { - @Override - public List loadDependencies(RemoteModRepository modRepository) throws IOException { - throw new IOException(); - } - - @Override - public Stream loadVersions(RemoteModRepository modRepository) throws IOException { - throw new IOException(); - } - })); + register(); LOG.info("JavaFX Version: " + System.getProperty("javafx.runtime.version")); try { @@ -163,6 +128,45 @@ public Stream loadVersions(RemoteModRepository modRepository) } } + private static void register() { + Skin.registerDefaultSkinLoader((type) -> { + switch (type) { + case ALEX: + return Skin.class.getResourceAsStream("/assets/img/skin/alex.png"); + case ARI: + return Skin.class.getResourceAsStream("/assets/img/skin/ari.png"); + case EFE: + return Skin.class.getResourceAsStream("/assets/img/skin/efe.png"); + case KAI: + return Skin.class.getResourceAsStream("/assets/img/skin/kai.png"); + case MAKENA: + return Skin.class.getResourceAsStream("/assets/img/skin/makena.png"); + case NOOR: + return Skin.class.getResourceAsStream("/assets/img/skin/noor.png"); + case STEVE: + return Skin.class.getResourceAsStream("/assets/img/skin/steve.png"); + case SUNNY: + return Skin.class.getResourceAsStream("/assets/img/skin/sunny.png"); + case ZURI: + return Skin.class.getResourceAsStream("/assets/img/skin/zuri.png"); + default: + return null; + } + }); + + RemoteMod.registerEmptyRemoteMod(new RemoteMod("", "", i18n("mods.broken_dependency.title"), i18n("mods.broken_dependency.desc"), new ArrayList<>(), "", "/assets/img/icon@8x.png", new RemoteMod.IMod() { + @Override + public List loadDependencies(RemoteModRepository modRepository) throws IOException { + throw new IOException(); + } + + @Override + public Stream loadVersions(RemoteModRepository modRepository) throws IOException { + throw new IOException(); + } + })); + } + private static ButtonType showAlert(AlertType alertType, String contentText, ButtonType... buttons) { return new Alert(alertType, contentText, buttons).showAndWait().orElse(null); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index c1571d6b93..2c08aab76a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -45,10 +45,7 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.SimpleMultimap; -import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.TaskCancellationAction; +import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.versioning.VersionNumber; @@ -64,6 +61,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.jackhuang.hmcl.mod.RemoteMod.DependencyType.*; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; @@ -271,7 +269,7 @@ protected ModDownloadPageSkin(DownloadPage control) { } if (control.repository.getType() != RemoteModRepository.Type.MOD) { - Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies.embedded")); + Node title = ComponentList.createComponentListTitle(i18n("mods.dependency.embedded")); ComponentList dependencyPane = new ComponentList(Lang::immutableListOf); Task.supplyAsync(() -> control.addon.getData().loadDependencies(control.repository).stream() .map(remoteMod -> new DependencyModItem(getSkinnable().page, remoteMod, control.version, control.callback)) @@ -329,6 +327,15 @@ protected ModDownloadPageSkin(DownloadPage control) { } private static final class DependencyModItem extends StackPane { + public static final EnumMap I18N_KEY = new EnumMap<>(Lang.mapOf( + Pair.pair(EMBEDDED, "mods.dependency.embedded"), + Pair.pair(OPTIONAL, "mods.dependency.optional"), + Pair.pair(REQUIRED, "mods.dependency.required"), + Pair.pair(TOOL, "mods.dependency.tool"), + Pair.pair(INCLUDE, "mods.dependency.include"), + Pair.pair(INCOMPATIBLE, "mods.dependency.incompatible"), + Pair.pair(BROKEN, "mods.dependency.broken") + )); DependencyModItem(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, DownloadCallback callback) { HBox pane = new HBox(8); @@ -361,6 +368,11 @@ private static final class ModItem extends StackPane { ModItem(RemoteMod.Version dataItem, DownloadPage selfPage) { VBox pane = new VBox(8); + { + Pane placeholder = new Pane(); + pane.getChildren().add(placeholder); + } + { HBox descPane = new HBox(8); descPane.setPadding(new Insets(8)); @@ -371,7 +383,7 @@ private static final class ModItem extends StackPane { EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); try { for (RemoteMod.Dependency dependency : dataItem.getDependencies()) { - if (dependency.getType() == RemoteMod.DependencyType.INCOMPATIBLE || dependency.getType() == RemoteMod.DependencyType.BROKEN) { + if (dependency.getType() == INCOMPATIBLE || dependency.getType() == BROKEN) { continue; } @@ -387,7 +399,7 @@ private static final class ModItem extends StackPane { } for (Map.Entry> entry : dependencies.entrySet()) { - dependenciesBox.getChildren().add(new Label(i18n("mods.dependency." + entry.getKey().name().toLowerCase()))); // TODO + dependenciesBox.getChildren().add(new Label(i18n(DependencyModItem.I18N_KEY.get(entry.getKey())))); dependenciesBox.getChildren().addAll(entry.getValue()); } pane.getChildren().add(dependenciesBox); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 361a5af996..f2440355be 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -868,7 +868,13 @@ mods.check_updates.target_version=Target Version mods.check_updates.update=Update mods.choose_mod=Choose a mod mods.curseforge=CurseForge -mods.dependencies=Dependencies +mods.dependency.embedded=built-in pre-mod (already packaged in the mod file by the author, no need to download separately) +mods.dependency.optional=optional pre-mod (if the game is missing, but mod functionality may be missing) +mods.dependency.required=required pre-mod (must be downloaded separately, missing may cause the game to fail to launch) +mods.dependency.tool=precursor library (must be downloaded separately, missing may cause the game to fail to launch) +mods.dependency.include=built-in pre-mod (already packaged in the mod file by the author, no need to download separately) +mods.dependency.incompatible=incompatible mod (installing both the mod and the mod being downloaded will cause the game to fail to launch) +mods.dependency.broken=Broken pre-mod (This premod used to exist on the mod repository, but is now deleted.) Try a different download source. mods.disable=Disable mods.download=Mod Download mods.download.title=Mod Download - %1s diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index d0af7e4d4c..987cbf3fa6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -800,7 +800,6 @@ mods.check_updates.target_version=Versión de destino mods.check_updates.update=Actualización mods.choose_mod=Elige un mod mods.curseforge=CurseForge -mods.dependencies=Dependencias mods.disable=Desactivar mods.download=Descarga de mods mods.download.title=Descarga de mods - %1s diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index 2098083964..52efa4e829 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -634,7 +634,6 @@ mods.check_updates.target_version=Target mods.check_updates.update=更新 mods.choose_mod=modを選択してください mods.curseforge=CurseForge -mods.dependencies=Dependencies mods.disable=無効にする mods.download=Modのダウンロード mods.download.title=Modダウンロード- %1s diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index b26c3b6bf2..d4282d956e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -639,7 +639,6 @@ mods.check_updates.target_version=Цель mods.check_updates.update=Обновить mods.choose_mod=Выбрать свои моды mods.curseforge=CurseForge -mods.dependencies=Зависимости mods.disable=Отключить mods.download=Скачивание модов mods.download.title=Скачивание модов - %1s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index d5a388fcce..3c1e00f32c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -741,7 +741,13 @@ mods.check_updates.target_version=目標版本 mods.check_updates.update=更新 mods.choose_mod=選擇模組 mods.curseforge=CurseForge -mods.dependencies=前置 Mod +mods.dependency.embedded=內置前端模組(作者已經打包在模組檔中,無需額外下載) +mods.dependency.optional=可選的前模組(如果缺少遊戲,遊戲可以運行,但模組功能可能缺失) +mods.dependency.required=必需的預模式(必須單獨下載,缺少可能會導致遊戲無法啟動) +mods.dependency.tool=前端庫(必須單獨下載,缺少可能會導致遊戲無法啟動) +mods.dependency.include=內置前綴模組(作者已經打包在模組檔中,無需額外下載) +mods.dependency.incompatible=模組不相容(同時安裝模組和模組都下載會導致遊戲無法啟動) +mods.dependency.broken=損壞的前綴(這個前綴曾經存在於 mod 倉庫中,但現在已被刪除)。嘗試其他下載源。 mods.disable=停用 mods.download=模組下載 mods.download.title=模組下載 - %1s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index d65ce2a476..88b81ab4a0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -741,7 +741,13 @@ mods.check_updates.target_version=目标版本 mods.check_updates.update=更新 mods.choose_mod=选择模组 mods.curseforge=CurseForge -mods.dependencies=前置 Mod +mods.dependency.embedded=内置的前置模组(已经由作者打包在模组文件中,无需另外下载) +mods.dependency.optional=可选的前置模组(若缺失游戏能够正常运行,但模组功能可能缺失) +mods.dependency.required=必须的前置模组(必须另外下载,缺失可能会导致游戏无法启动) +mods.dependency.tool=前置库(必须另外下载,缺失可能会导致游戏无法启动) +mods.dependency.include=内置的前置模组(已经由作者打包在模组文件中,无需另外下载) +mods.dependency.incompatible=不兼容的模组(同时安装该模组和正在下载的模组会导致游戏无法启动) +mods.dependency.broken=损坏的前置模组(该前置模组曾经在该模组仓库上存在过,但现在被删除了。换个下载源试试吧。) mods.disable=禁用 mods.download=模组下载 mods.download.title=模组下载 - %1s From 73510bae87b5f619f24e716fca4828a0076de439 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 28 Jul 2023 21:04:28 +0800 Subject: [PATCH 006/104] Enhance UI --- .../hmcl/ui/versions/DownloadPage.java | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 2c08aab76a..ba049dd75f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -275,10 +275,18 @@ protected ModDownloadPageSkin(DownloadPage control) { .map(remoteMod -> new DependencyModItem(getSkinnable().page, remoteMod, control.version, control.callback)) .collect(Collectors.toList()) ).whenComplete(Schedulers.javafx(), (result, exception) -> { + dependencyPane.getContent().clear(); if (exception == null) { - dependencyPane.getContent().setAll(result); + for (DependencyModItem element : result) { + VBox box = new VBox(); + box.setPadding(new Insets(8,0,8,0)); + box.getChildren().setAll(element); + dependencyPane.getContent().add(box); + } } else { - dependencyPane.getContent().setAll(new Label(i18n("download.failed.refresh"))); + Label msg = new Label(i18n("download.failed.refresh")); + msg.setPadding(new Insets(8)); + dependencyPane.getContent().setAll(msg); } }).start(); dependencyPane.getStyleClass().add("no-padding"); @@ -339,7 +347,7 @@ private static final class DependencyModItem extends StackPane { DependencyModItem(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, DownloadCallback callback) { HBox pane = new HBox(8); - pane.setPadding(new Insets(8)); + pane.setPadding(new Insets(0, 8, 0, 8)); pane.setAlignment(Pos.CENTER_LEFT); TwoLineListItem content = new TwoLineListItem(); HBox.setHgrow(content, Priority.ALWAYS); @@ -367,19 +375,11 @@ private static final class ModItem extends StackPane { ModItem(RemoteMod.Version dataItem, DownloadPage selfPage) { VBox pane = new VBox(8); + pane.setPadding(new Insets(8, 0, 8, 0)); { - Pane placeholder = new Pane(); - pane.getChildren().add(placeholder); - } - - { - HBox descPane = new HBox(8); - descPane.setPadding(new Insets(8)); - descPane.setAlignment(Pos.CENTER_LEFT); - if (selfPage.repository.getType() == RemoteModRepository.Type.MOD) { - VBox dependenciesBox = new VBox(); + List elements = new ArrayList<>(); EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); try { for (RemoteMod.Dependency dependency : dataItem.getDependencies()) { @@ -394,17 +394,25 @@ private static final class ModItem extends StackPane { } } catch (IOException exception) { dependencies.clear(); - dependenciesBox.getChildren().setAll(new Label(i18n("download.failed.refresh"))); + Label msg = new Label(i18n("download.failed.refresh")); + msg.setPadding(new Insets(8)); + elements.add(msg); LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid())); } for (Map.Entry> entry : dependencies.entrySet()) { - dependenciesBox.getChildren().add(new Label(i18n(DependencyModItem.I18N_KEY.get(entry.getKey())))); - dependenciesBox.getChildren().addAll(entry.getValue()); + Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(entry.getKey()))); + title.setPadding(new Insets(0, 8, 0, 8)); + elements.add(title); + elements.addAll(entry.getValue()); } - pane.getChildren().add(dependenciesBox); + pane.getChildren().addAll(elements); } + HBox descPane = new HBox(8); + descPane.setPadding(new Insets(0, 8, 0, 8)); + descPane.setAlignment(Pos.CENTER_LEFT); + { StackPane graphicPane = new StackPane(); graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24)); From 4b8a5f0b03f2426c7fb41320d6a3ccf7aba9921f Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 28 Jul 2023 21:29:22 +0800 Subject: [PATCH 007/104] Code cleanup --- .../jackhuang/hmcl/ui/versions/DownloadPage.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index ba049dd75f..ac8e559d1f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -273,16 +273,16 @@ protected ModDownloadPageSkin(DownloadPage control) { ComponentList dependencyPane = new ComponentList(Lang::immutableListOf); Task.supplyAsync(() -> control.addon.getData().loadDependencies(control.repository).stream() .map(remoteMod -> new DependencyModItem(getSkinnable().page, remoteMod, control.version, control.callback)) + .map(dependencyModItem -> { + VBox box = new VBox(); + box.setPadding(new Insets(8,0,8,0)); + box.getChildren().setAll(dependencyModItem); + return box; + }) .collect(Collectors.toList()) ).whenComplete(Schedulers.javafx(), (result, exception) -> { - dependencyPane.getContent().clear(); if (exception == null) { - for (DependencyModItem element : result) { - VBox box = new VBox(); - box.setPadding(new Insets(8,0,8,0)); - box.getChildren().setAll(element); - dependencyPane.getContent().add(box); - } + dependencyPane.getContent().setAll(result); } else { Label msg = new Label(i18n("download.failed.refresh")); msg.setPadding(new Insets(8)); From 0e460c9a84ba2bce287574a28b8890a035270e7c Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 29 Jul 2023 10:23:44 +0800 Subject: [PATCH 008/104] Enhance UI --- .../main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index ac8e559d1f..e9e080f64e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -275,7 +275,7 @@ protected ModDownloadPageSkin(DownloadPage control) { .map(remoteMod -> new DependencyModItem(getSkinnable().page, remoteMod, control.version, control.callback)) .map(dependencyModItem -> { VBox box = new VBox(); - box.setPadding(new Insets(8,0,8,0)); + box.setPadding(new Insets(8, 0, 8, 0)); box.getChildren().setAll(dependencyModItem); return box; }) From 87ea8467330dac94e9f234628adca0a1997e0086 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 29 Jul 2023 13:09:52 +0800 Subject: [PATCH 009/104] Manually sort the result from curseforge when searching mods by name. --- .../curse/CurseForgeRemoteModRepository.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index b67104e40f..40b8cd5df5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -22,6 +22,7 @@ import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.util.MurmurHash2; +import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.io.HttpRequest; import org.jackhuang.hmcl.util.io.JarUtils; import org.jetbrains.annotations.Nullable; @@ -107,7 +108,33 @@ public Stream search(String gameVersion, @Nullable RemoteModRepositor .header("X-API-KEY", apiKey) .getJson(new TypeToken>>() { }.getType()); - return response.getData().stream().map(CurseAddon::toMod); + Stream res = response.getData().stream().map(CurseAddon::toMod); + if (sortType != SortType.NAME) { + return res; + } + + // https://github.com/huanghongxun/HMCL/issues/1549 + return res.map(remoteMod -> { + if (searchFilter.length() == 0 || remoteMod.getTitle().length() == 0) { + return pair(remoteMod, Math.max(searchFilter.length(), remoteMod.getTitle().length())); + } + int[][] lev = new int[searchFilter.length() + 1][remoteMod.getTitle().length() + 1]; + for (int i = 0; i < remoteMod.getTitle().length() + 1; i++) { + lev[0][i] = i; + } + for (int i = 0; i < searchFilter.length() + 1; i++) { + lev[i][0] = i; + } + for (int i = 1; i < searchFilter.length() + 1; i++) { + for (int j = 1; j < remoteMod.getTitle().length() + 1; j++) { + int countByInsert = lev[i][j - 1] + 1; + int countByDel = lev[i - 1][j] + 1; + int countByReplace = searchFilter.charAt(i - 1) == remoteMod.getTitle().charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; + lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); + } + } + return pair(remoteMod, lev[searchFilter.length()][remoteMod.getTitle().length()]); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey); } @Override From ed355d84c890a539391cec12b421558e8c9194ef Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 29 Jul 2023 17:52:01 +0800 Subject: [PATCH 010/104] Render the search results from remote mod repositories in several pages. --- .../game/LocalizedRemoteModRepository.java | 2 +- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 7 ++ .../hmcl/ui/versions/DownloadListPage.java | 87 +++++++++++++++---- .../resources/assets/lang/I18N.properties | 5 ++ .../resources/assets/lang/I18N_zh.properties | 5 ++ .../assets/lang/I18N_zh_CN.properties | 5 ++ .../hmcl/mod/RemoteModRepository.java | 21 ++++- .../curse/CurseForgeRemoteModRepository.java | 8 +- .../modrinth/ModrinthRemoteModRepository.java | 6 +- 9 files changed, 122 insertions(+), 24 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java index d90d2a1dea..74903aeca8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java @@ -35,7 +35,7 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor protected abstract RemoteModRepository getBackedRemoteModRepository(); @Override - public Stream search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException { + public SearchResult search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException { String newSearchFilter; if (StringUtils.containsChinese(searchFilter)) { ModTranslations modTranslations = ModTranslations.getTranslationsByRepositoryType(getType()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 67a7c7012a..670923cdcd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -678,6 +678,13 @@ public static JFXButton newRaisedButton(String text) { return button; } + public static JFXButton newBorderButton(String text) { + JFXButton button = new JFXButton(text); + button.getStyleClass().add("jfx-button-border"); + button.setButtonType(JFXButton.ButtonType.RAISED); + return button; + } + public static void applyDragListener(Node node, FileFilter filter, Consumer> callback) { applyDragListener(node, filter, callback, null); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 07d21e1ac5..3406b280a3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -77,6 +77,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP private final BooleanProperty failed = new SimpleBooleanProperty(false); private final boolean versionSelection; private final ObjectProperty version = new SimpleObjectProperty<>(); + private final IntegerProperty pageOffset = new SimpleIntegerProperty(0); + private final IntegerProperty pageCount = new SimpleIntegerProperty(-1); private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); private final ObservableList versions = FXCollections.observableArrayList(); private final StringProperty selectedVersion = new SimpleStringProperty(); @@ -178,10 +180,12 @@ public void search(String userGameVersion, RemoteModRepository.Category category }).whenComplete(Schedulers.javafx(), (result, exception) -> { setLoading(false); if (exception == null) { - items.setAll(result.collect(Collectors.toList())); + items.setAll(result.getResults().collect(Collectors.toList())); + pageCount.set(result.getTotalPages()); failed.set(false); } else { failed.set(true); + pageCount.set(-1); retrySearch = () -> search(userGameVersion, category, pageOffset, searchFilter, sort); } }).executor(true); @@ -221,8 +225,6 @@ protected Skin createDefaultSkin() { } private static class ModDownloadListPageSkin extends SkinBase { - private final AggregatedObservableList actions = new AggregatedObservableList<>(); - protected ModDownloadListPageSkin(DownloadListPage control) { super(control); @@ -341,25 +343,80 @@ protected ModDownloadListPageSkin(DownloadListPage control) { sortComboBox.getSelectionModel().select(0); searchPane.addRow(rowIndex++, new Label(i18n("mods.category")), categoryStackPane, new Label(i18n("search.sort")), sortStackPane); - JFXButton searchButton = FXUtils.newRaisedButton(i18n("search")); - ObservableList last = FXCollections.observableArrayList(searchButton); - HBox searchBox = new HBox(8); - actions.appendList(control.actions); - actions.appendList(last); - Bindings.bindContent(searchBox.getChildren(), actions.getAggregatedList()); - GridPane.setColumnSpan(searchBox, 4); - searchBox.setAlignment(Pos.CENTER_RIGHT); - searchPane.addRow(rowIndex++, searchBox); - EventHandler searchAction = e -> getSkinnable() .search(gameVersionField.getSelectionModel().getSelectedItem(), Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem()) .map(CategoryIndented::getCategory) .orElse(null), - 0, + control.pageOffset.get(), nameField.getText(), sortComboBox.getSelectionModel().getSelectedItem()); - searchButton.setOnAction(searchAction); + + HBox actionsBox = new HBox(8); + GridPane.setColumnSpan(actionsBox, 4); + actionsBox.setAlignment(Pos.CENTER); + { + AggregatedObservableList actions = new AggregatedObservableList<>(); + + JFXButton firstPageButton = FXUtils.newBorderButton(i18n("search.first_page")); + firstPageButton.setOnAction(event -> { + control.pageOffset.set(0); + searchAction.handle(event); + }); + firstPageButton.setDisable(true); + control.pageCount.addListener((observable, oldValue, newValue) -> firstPageButton.setDisable(control.pageCount.get() == -1)); + + JFXButton previousPageButton = FXUtils.newBorderButton(i18n("search.previous_page")); + previousPageButton.setOnAction(event -> { + if (control.pageOffset.get() > 0) { + control.pageOffset.set(control.pageOffset.get() - 1); + searchAction.handle(event); + } + }); + previousPageButton.setDisable(true); + control.pageOffset.addListener((observable, oldValue, newValue) -> previousPageButton.setDisable( + control.pageCount.get() == -1 || control.pageOffset.get() == 0 + )); + + Label pageOffset = new Label(i18n("search.page_n", 0, "-")); + control.pageOffset.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n( + "search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString() + ))); + control.pageCount.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n( + "search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString() + ))); + + JFXButton nextPageButton = FXUtils.newBorderButton(i18n("search.next_page")); + nextPageButton.setOnAction(event -> { + control.pageOffset.set(control.pageOffset.get() + 1); + searchAction.handle(event); + }); + nextPageButton.setDisable(true); + control.pageOffset.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable( + control.pageCount.get() == -1 || control.pageOffset.get() == control.pageCount.get() - 1 + )); + + JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page")); + lastPageButton.setOnAction(event -> { + control.pageOffset.set(control.pageCount.get() - 1); + searchAction.handle(event); + }); + lastPageButton.setDisable(true); + control.pageCount.addListener((observable, oldValue, newValue) -> lastPageButton.setDisable(control.pageCount.get() == -1)); + + Pane placeholder = new Pane(); + HBox.setHgrow(placeholder, Priority.SOMETIMES); + + JFXButton searchButton = FXUtils.newRaisedButton(i18n("search")); + searchButton.setOnAction(searchAction); + + actions.appendList(FXCollections.observableArrayList(firstPageButton, previousPageButton, pageOffset, nextPageButton, lastPageButton, placeholder, searchButton)); + actions.appendList(control.actions); + Bindings.bindContent(actionsBox.getChildren(), actions.getAggregatedList()); + } + + searchPane.addRow(rowIndex++, actionsBox); + nameField.setOnAction(searchAction); gameVersionField.setOnAction(searchAction); categoryComboBox.setOnAction(searchAction); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 361a5af996..e1573a0344 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -976,6 +976,11 @@ search.hint.chinese=Search queries support both Chinese and English search.hint.english=Only English is supported search.enter=Enter text here search.sort=Sort By +search.first_page=The first page +search.previous_page=The previous page +search.next_page=The next page +search.last_page=The last page +search.page_n=Page No.%d, %s pages in total selector.choose=Choose selector.choose_file=Select a file diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 6fc9212640..edef929f5a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -845,6 +845,11 @@ search.hint.chinese=支援中英文搜尋 search.hint.english=僅支援英文搜尋 search.enter=請在此處輸入 search.sort=排序 +search.first_page=第一頁 +search.previous_page=上一頁 +search.next_page=下一頁 +search.last_page=最後一頁 +search.page_n=第 %d 頁,共 %s 頁 selector.choose=選擇 selector.choose_file=選擇檔案 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 4073a113d5..bf1a505b96 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -845,6 +845,11 @@ search.hint.chinese=支持中英文搜索 search.hint.english=仅支持英文搜索 search.enter=可在此处输入 search.sort=排序 +search.first_page=第一页 +search.previous_page=上一页 +search.next_page=下一页 +search.last_page=最后一页 +search.page_n=第 %d 页,共 %s 页 selector.choose=选择 selector.choose_file=选择文件 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java index 0ed37ae4ac..a33a374693 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java @@ -51,7 +51,26 @@ enum SortOrder { DESC } - Stream search(String gameVersion, @Nullable Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) + class SearchResult { + private final Stream results; + + private final int totalPages; + + public SearchResult(Stream results, int pages) { + this.results = results; + this.totalPages = pages; + } + + public Stream getResults() { + return this.results; + } + + public int getTotalPages() { + return this.totalPages; + } + } + + SearchResult search(String gameVersion, @Nullable Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException; Optional getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 40b8cd5df5..98c6936b56 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -92,7 +92,7 @@ private String toSortOrder(SortOrder sortOrder) { } @Override - public Stream search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException { + 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(); Response> response = HttpRequest.GET(PREFIX + "/v1/mods/search", @@ -110,11 +110,11 @@ public Stream search(String gameVersion, @Nullable RemoteModRepositor }.getType()); Stream res = response.getData().stream().map(CurseAddon::toMod); if (sortType != SortType.NAME) { - return res; + return new SearchResult(res, response.pagination.totalCount); } // https://github.com/huanghongxun/HMCL/issues/1549 - return res.map(remoteMod -> { + return new SearchResult(res.map(remoteMod -> { if (searchFilter.length() == 0 || remoteMod.getTitle().length() == 0) { return pair(remoteMod, Math.max(searchFilter.length(), remoteMod.getTitle().length())); } @@ -134,7 +134,7 @@ public Stream search(String gameVersion, @Nullable RemoteModRepositor } } return pair(remoteMod, lev[searchFilter.length()][remoteMod.getTitle().length()]); - }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.pagination.totalCount); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index f367489e29..9c5460472f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -75,7 +75,7 @@ private static String convertSortType(SortType sortType) { } @Override - public Stream search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException { + public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException { List> facets = new ArrayList<>(); facets.add(Collections.singletonList("project_type:" + projectType)); if (StringUtils.isNotBlank(gameVersion)) { @@ -87,14 +87,14 @@ public Stream search(String gameVersion, @Nullable RemoteModRepositor Map query = mapOf( pair("query", searchFilter), pair("facets", JsonUtils.UGLY_GSON.toJson(facets)), - pair("offset", Integer.toString(pageOffset)), + pair("offset", Integer.toString(pageOffset * pageSize)), pair("limit", Integer.toString(pageSize)), pair("index", convertSortType(sort)) ); Response response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query)) .getJson(new TypeToken>() { }.getType()); - return response.getHits().stream().map(ProjectSearchResult::toMod); + return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int)Math.ceil((double)response.totalHits / pageSize)); } @Override From 978742a80cb2388862f77dd04dabbbb289476bd6 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 29 Jul 2023 19:29:01 +0800 Subject: [PATCH 011/104] Fix merge --- .../java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index a724e96399..51c98e1596 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -62,7 +62,6 @@ public TwoLineListItem() { Label tagLabel = new Label(); tagLabel.getStyleClass().add("tag"); tagLabel.setText(tag); - FXUtils.installFastTooltip(tagLabel, tag); HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); return tagLabel; }); From ae49422a17541d80c8c17d143edcfd142573914c Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 29 Jul 2023 19:32:30 +0800 Subject: [PATCH 012/104] Fix --- .../java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 3406b280a3..c583feb87d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -395,6 +395,9 @@ protected ModDownloadListPageSkin(DownloadListPage control) { control.pageOffset.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable( control.pageCount.get() == -1 || control.pageOffset.get() == control.pageCount.get() - 1 )); + control.pageCount.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable( + control.pageCount.get() == -1 || control.pageOffset.get() == control.pageCount.get() - 1 + )); JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page")); lastPageButton.setOnAction(event -> { From 47795972c8a0fd88ea2bdefbd2e946a4fbabac36 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 29 Jul 2023 19:48:30 +0800 Subject: [PATCH 013/104] Add a button which navigates to the modpack download page in the modpack installl page --- .../org/jackhuang/hmcl/ui/download/DownloadPage.java | 4 ++++ .../jackhuang/hmcl/ui/download/ModpackSelectionPage.java | 9 ++++++++- .../src/main/resources/assets/lang/I18N_zh_CN.properties | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index 109df16b5f..8b6619a154 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -232,6 +232,10 @@ public void showGameDownloads() { tab.select(newGameTab); } + public void showModpackDownloads() { + tab.select(modpackTab); + } + public void showModDownloads() { tab.select(modTab); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java index 31987d15f5..12c7a25c4d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java @@ -69,7 +69,8 @@ public ModpackSelectionPage(WizardController controller) { this.getChildren().setAll( title, createButton("local", this::onChooseLocalFile), - createButton("remote", this::onChooseRemoteFile) + createButton("remote", this::onChooseRemoteFile), + createButton("repository", this::onChooseRepository) ); Optional filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class); @@ -168,6 +169,12 @@ private void onChooseRemoteFile() { }); } + public void onChooseRepository() { + DownloadPage downloadPage = new DownloadPage(); + downloadPage.showModpackDownloads(); + Controllers.navigate(downloadPage); + } + @Override public void cleanup(Map settings) { } diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 78039a97f6..b1e4f598e0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -616,6 +616,8 @@ modpack.choose.local=导入本地整合包文件 modpack.choose.local.detail=你可以直接将整合包文件拖入本页面以安装 modpack.choose.remote=从互联网下载整合包 modpack.choose.remote.detail=需要提供整合包的下载链接 +modpack.choose.repository=从 Curseforge / Modrinth 下载整合包 +modpack.choose.repository.detail=下载后记得回到这个界面,把整合包拖进来哦 modpack.choose.remote.tooltip=要下载的整合包的链接 modpack.completion=下载整合包相关文件 modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 HTML(图片请用网络图) From 6d728739b5c9b652f92ec8d5bdd6a9ccf21d761f Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 29 Jul 2023 19:52:34 +0800 Subject: [PATCH 014/104] Fix I18N --- HMCL/src/main/resources/assets/lang/I18N.properties | 2 ++ HMCL/src/main/resources/assets/lang/I18N_zh.properties | 2 ++ 2 files changed, 4 insertions(+) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index a73ef58a68..40fdb64732 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -740,6 +740,8 @@ modpack.choose.local=Import from local file modpack.choose.local.detail=You can drag and drop the modpack file here modpack.choose.remote=Download from the Internet modpack.choose.remote.detail=A direct download link to the remote modpack file is required +modpack.choose.repository=Download a modpack from Curseforge or Modrinth +modpack.choose.repository.detail=Remember to go back to this page and drop the modpack file here after the modpack is downloaded modpack.choose.remote.tooltip=Please enter your modpack URL modpack.completion=Downloading dependencies modpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 3f6827251c..d09859e900 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -616,6 +616,8 @@ modpack.choose.local=匯入本機模組包檔案 modpack.choose.local.detail=你可以直接將模組包檔案拖入本頁面以安裝 modpack.choose.remote=從網路下載模組包 modpack.choose.remote.detail=需要提供模組包的下載連結 +modpack.choose.repository= 從 Curseforge / Modrinth 下載整合包 +modpack.choose.repository.detail=下載后記得回到這個界面,把整合包拖進來哦 modpack.choose.remote.tooltip=要下載的模組包的連結 modpack.completion=下載模組包相關檔案 modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新記錄,支援 Markdown(圖片請上傳至網路)。 From e8601466fe2629e50e2e95f55396379c3b9464b3 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 29 Jul 2023 21:40:21 +0800 Subject: [PATCH 015/104] Render the mod loaders supported by the version in mod info page. --- .../hmcl/ui/versions/ModListPageSkin.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 4cccb6dd4a..d7a2d25372 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -32,10 +32,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.mod.LocalModFile; -import org.jackhuang.hmcl.mod.ModManager; -import org.jackhuang.hmcl.mod.RemoteMod; -import org.jackhuang.hmcl.mod.RemoteModRepository; +import org.jackhuang.hmcl.mod.*; import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; import org.jackhuang.hmcl.setting.Profile; @@ -318,7 +315,7 @@ class ModInfoDialog extends JFXDialogLayout { setBody(description); if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) { - Lang.>immutableListOf( + Lang.>immutableListOf( pair("mods.curseforge", CurseForgeRemoteModRepository.MODS), pair("mods.modrinth", ModrinthRemoteModRepository.MODS) ).forEach(item -> { @@ -331,6 +328,19 @@ class ModInfoDialog extends JFXDialogLayout { if (versionOptional.isPresent()) { RemoteMod remoteMod = remoteModRepository.getModById(versionOptional.get().getModid()); FXUtils.runInFX(() -> { + for (ModLoaderType modLoaderType : versionOptional.get().getLoaders()) { + switch (modLoaderType) { + case FABRIC: + case FORGE: + case LITE_LOADER: + case QUILT: { + if (!title.getTags().contains(modLoaderType.name())) { + title.getTags().add(modLoaderType.name()); + } + } + } + } + button.setOnAction(e -> { fireEvent(new DialogCloseEvent()); Controllers.navigate(new DownloadPage( From ab64be9307a2b1f34e5ff0a2cbb6da501b1cf9ca Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 30 Jul 2023 13:47:40 +0800 Subject: [PATCH 016/104] Fix #2104 --- .../hmcl/mod/modrinth/ModrinthRemoteModRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index fdce4f50aa..b66b4729a2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -308,9 +308,9 @@ public RemoteMod toMod() { title, description, categories, - null, + String.format("https://modrinth.com/%s/%s", projectType, id), iconUrl, - (RemoteMod.IMod) this + this ); } } From adfa7287f8da3b3ebbeb389a8927e1324847f619 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 1 Aug 2023 14:10:36 +0800 Subject: [PATCH 017/104] Enhance TwoLineListItem --- .../hmcl/ui/construct/TwoLineListItem.java | 171 +++++++++++++++--- .../hmcl/ui/versions/DownloadListPage.java | 1 + .../hmcl/ui/versions/DownloadPage.java | 3 + HMCL/src/main/resources/assets/css/root.css | 38 ++-- 4 files changed, 172 insertions(+), 41 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 51c98e1596..a7ddf27fcc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -17,35 +17,56 @@ */ package org.jackhuang.hmcl.ui.construct; +import javafx.animation.Animation; +import javafx.animation.Interpolator; +import javafx.animation.TranslateTransition; +import javafx.application.Platform; import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; +import org.jackhuang.hmcl.setting.ConfigHolder; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.AggregatedObservableList; +import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.MappedObservableList; public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; + private static final double TITLE_PART = 0.7D; private final StringProperty title = new SimpleStringProperty(this, "title"); private final ObservableList tags = FXCollections.observableArrayList(); private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle"); - private final ObservableList tagLabels; - private final AggregatedObservableList firstLineChildren; + private final Label titleLabel; + private final HBox tagBox; + private final Label subTitleLabel; + + private final BooleanProperty titleTranslatePlaying = new SimpleBooleanProperty(false); + private final BooleanProperty tagTranslatePlaying = new SimpleBooleanProperty(false); + private final BooleanProperty subTitleTranslatePlaying = new SimpleBooleanProperty(false); public TwoLineListItem(String titleString, String subtitleString) { this(); title.set(titleString); subtitle.set(subtitleString); + + Platform.runLater(this::reLayout); } public TwoLineListItem() { @@ -54,28 +75,55 @@ public TwoLineListItem() { HBox firstLine = new HBox(); firstLine.getStyleClass().add("first-line"); - Label lblTitle = new Label(); - lblTitle.getStyleClass().add("title"); - lblTitle.textProperty().bind(title); - - tagLabels = MappedObservableList.create(tags, tag -> { - Label tagLabel = new Label(); - tagLabel.getStyleClass().add("tag"); - tagLabel.setText(tag); - HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); - return tagLabel; - }); - firstLineChildren = new AggregatedObservableList<>(); - firstLineChildren.appendList(FXCollections.singletonObservableList(lblTitle)); - firstLineChildren.appendList(tagLabels); - Bindings.bindContent(firstLine.getChildren(), firstLineChildren.getAggregatedList()); - - Label lblSubtitle = new Label(); - lblSubtitle.getStyleClass().add("subtitle"); - lblSubtitle.textProperty().bind(subtitle); - - HBox secondLine = new HBox(); - secondLine.getChildren().setAll(lblSubtitle); + { + Pane titlePane = new Pane(); + titlePane.getStyleClass().add("title-pane"); + + titleLabel = new Label(); + titleLabel.getStyleClass().add("title"); + titleLabel.textProperty().bind(BindingMapping.of(title).map(s -> s + " | " + s)); + + Rectangle titleClip = new Rectangle(0, 0, titleLabel.getWidth() / 2 - 10, titleLabel.getHeight()); + titleClip.widthProperty().bind(BindingMapping.of(titleLabel.widthProperty()).map(w -> w.doubleValue() / 2 - 10)); + titlePane.setClip(titleClip); + titlePane.getChildren().setAll(titleLabel); + + Pane tagLabelsPane = new Pane(); + tagLabelsPane.getStyleClass().add("tag-pane"); + + tagBox = new HBox(); + AggregatedObservableList tagLabels = new AggregatedObservableList<>(); + for (int i = 0; i < 2; i++) { + tagLabels.appendList(MappedObservableList.create(tags, tag -> { + Label tagLabel = new Label(); + tagLabel.getStyleClass().add("tag"); + tagLabel.setText(tag); + HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); + return tagLabel; + })); + } + Bindings.bindContent(tagBox.getChildren(), tagLabels.getAggregatedList()); + + Rectangle tagClip = new Rectangle(0, 0, tagBox.getWidth() / 2 - 2, tagBox.getHeight()); + tagClip.widthProperty().bind(BindingMapping.of(tagBox.widthProperty()).map(w -> w.doubleValue() / 2 - 2)); + tagLabelsPane.setClip(tagClip); + tagLabelsPane.getChildren().setAll(tagBox); + + firstLine.getChildren().setAll(titlePane, tagLabelsPane); + } + + Pane secondLine = new Pane(); + { + subTitleLabel = new Label(); + subTitleLabel.getStyleClass().add("subtitle"); + subTitleLabel.textProperty().bind(BindingMapping.of(subtitle).map(s -> s + " | " + s)); + + Rectangle subTitleClip = new Rectangle(0, 0, subTitleLabel.getWidth() / 2 - 2, 80000); + subTitleClip.widthProperty().bind(BindingMapping.of(subTitleLabel.widthProperty()).map(w -> w.doubleValue() / 2 - 2)); + secondLine.setClip(subTitleClip); + + secondLine.getChildren().setAll(subTitleLabel); + } getChildren().setAll(firstLine, secondLine); @@ -85,6 +133,81 @@ public TwoLineListItem() { }); getStyleClass().add(DEFAULT_STYLE_CLASS); + + title.addListener(observable -> Platform.runLater(this::reLayout)); + subtitle.addListener(observable -> Platform.runLater(this::reLayout)); + tags.addListener((ListChangeListener) c -> Platform.runLater(this::reLayout)); + this.widthProperty().addListener(observable -> Platform.runLater(this::reLayout)); + + TranslateTransition titleTranslateTransition = new TranslateTransition(Duration.seconds(0.1D), titleLabel); + titleTranslateTransition.setFromX(0); + titleTranslateTransition.durationProperty().bind(BindingMapping.of(titleLabel.widthProperty()).map(w -> Duration.seconds((w.doubleValue() / 2D + 1) / 30D))); + titleTranslateTransition.toXProperty().bind(BindingMapping.of(titleLabel.widthProperty()).map(w -> -w.doubleValue() / 2D - 1)); + titleTranslateTransition.setInterpolator(Interpolator.LINEAR); + titleTranslateTransition.setCycleCount(Animation.INDEFINITE); + titleTranslatePlaying.addListener(generateTranslateListener(titleTranslateTransition)); + + TranslateTransition tagTranslateTransition = new TranslateTransition(Duration.seconds(0.1D), tagBox); + tagTranslateTransition.setFromX(0); + tagTranslateTransition.durationProperty().bind(BindingMapping.of(tagBox.widthProperty()).map(w -> Duration.seconds(w.doubleValue() / 2D / 30D))); + tagTranslateTransition.toXProperty().bind(BindingMapping.of(tagBox.widthProperty()).map(w -> -w.doubleValue() / 2D)); + tagTranslateTransition.setInterpolator(Interpolator.LINEAR); + tagTranslateTransition.setCycleCount(Animation.INDEFINITE); + tagTranslatePlaying.addListener(generateTranslateListener(tagTranslateTransition)); + + TranslateTransition subTitleTranslateTransition = new TranslateTransition(Duration.seconds(0.1D), subTitleLabel); + subTitleTranslateTransition.setFromX(0); + subTitleTranslateTransition.durationProperty().bind(BindingMapping.of(subTitleLabel.widthProperty()).map(w -> Duration.seconds(w.doubleValue() / 2D / 30D))); + subTitleTranslateTransition.toXProperty().bind(BindingMapping.of(subTitleLabel.widthProperty()).map(w -> (-w.doubleValue() - 8) / 2D)); + subTitleTranslateTransition.setInterpolator(Interpolator.LINEAR); + subTitleTranslateTransition.setCycleCount(Animation.INDEFINITE); + subTitleTranslatePlaying.addListener(generateTranslateListener(subTitleTranslateTransition)); + + Rectangle mainClip = new Rectangle(0, 0, this.getWidth(), this.getHeight()); + mainClip.widthProperty().bind(this.widthProperty()); + mainClip.heightProperty().bind(this.heightProperty()); + this.setClip(mainClip); + + this.minWidthProperty().set(0); + } + + private static ChangeListener generateTranslateListener(TranslateTransition translateTransition) { + if (ConfigHolder.config().isAnimationDisabled()) { + return (observable, oldValue, newValue) -> {}; + } else { + return (observable, oldValue, newValue) -> { + if (oldValue.booleanValue() != newValue.booleanValue()) { + if (newValue) { + translateTransition.play(); + } else { + translateTransition.jumpTo(Duration.ZERO); + translateTransition.stop(); + } + } + }; + } + } + + public void reLayout() { + this.layout(); + + double titleMaxWidth = tags.size() == 0 ? this.getWidth() : this.getWidth() * TITLE_PART; + double titleWidth = Math.min(titleLabel.getWidth() / 2, titleMaxWidth); + double tagMaxWidth = this.getWidth() - titleWidth - this.paddingProperty().get().getLeft() - this.paddingProperty().get().getRight() - 5; + double tagWidth = Math.min(tagBox.getWidth() / 2, tagMaxWidth); + + FXUtils.setLimitWidth((Pane) titleLabel.getParent(), titleWidth); + Rectangle titleClip = new Rectangle(0, 0, titleWidth - 10, titleLabel.getHeight()); + titleLabel.getParent().setClip(titleClip); + + titleTranslatePlaying.set(titleLabel.getWidth() / 2 > titleMaxWidth); + + Rectangle tagClip = new Rectangle(0, 0, tagWidth - 2, tagBox.getHeight()); + tagBox.getParent().setClip(tagClip); + + tagTranslatePlaying.set(tagBox.getWidth() / 2 > tagMaxWidth); + + subTitleTranslatePlaying.set(subTitleLabel.getWidth() / 2 > this.getWidth()); } public String getTitle() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 07d21e1ac5..76ad09c577 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -402,6 +402,7 @@ protected ModDownloadListPageSkin(DownloadListPage control) { pane.getChildren().add(container); container.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content); + HBox.setHgrow(content, Priority.ALWAYS); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index e20b06a329..2102864200 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -264,12 +264,14 @@ protected ModDownloadPageSkin(DownloadPage control) { JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod")); openMcmodButton.setExternalLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod)); descriptionPane.getChildren().add(openMcmodButton); + openMcmodButton.setMinWidth(Region.USE_PREF_SIZE); runInFX(() -> FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod"))); if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) { JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs")); openMcbbsButton.setExternalLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs())); descriptionPane.getChildren().add(openMcbbsButton); + openMcbbsButton.setMinWidth(Region.USE_PREF_SIZE); runInFX(() -> FXUtils.installFastTooltip(openMcbbsButton, i18n("mods.mcbbs"))); } } @@ -277,6 +279,7 @@ protected ModDownloadPageSkin(DownloadPage control) { JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage()); openUrlButton.setExternalLink(getSkinnable().addon.getPageUrl()); descriptionPane.getChildren().add(openUrlButton); + openUrlButton.setMinWidth(Region.USE_PREF_SIZE); runInFX(() -> FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage())); } diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index aea759d2a9..c21b81abca 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -155,12 +155,12 @@ -fx-padding: 0 0 0 10; } -.advanced-list-item > .rippler-container > .container > .two-line-list-item > .first-line > .title { +.advanced-list-item > .rippler-container > .container > .two-line-list-item > .first-line > .title-pane > .title { -fx-font-size: 13; -fx-text-alignment: justify; } -.advanced-list-item > .rippler-container > .container > .two-line-list-item > HBox > .subtitle { +.advanced-list-item > .rippler-container > .container > .two-line-list-item > Pane > .subtitle { -fx-font-size: 10; -fx-text-alignment: justify; } @@ -169,7 +169,7 @@ -fx-background-color: -fx-base-rippler-color; } -.advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > .first-line > .title { +.advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > .first-line > .title-pane > .title { -fx-text-fill: -fx-base-color; -fx-font-weight: bold; } @@ -182,11 +182,11 @@ -fx-padding: 0 0 0 0; } -.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title { +.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title-pane > .title { -fx-font-size: 13; } -.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > HBox > .subtitle { +.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > Pane > .subtitle { -fx-font-size: 10; } @@ -198,7 +198,7 @@ -fx-background-color: -fx-base-rippler-color; } -.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title { +.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title-pane > .title { -fx-text-fill: -fx-base-color; -fx-font-weight: bold; } @@ -261,22 +261,26 @@ -fx-padding: 4 0 4 0; } -.two-line-list-item > .first-line > .title { +.two-line-list-item > .first-line > .title-pane { + -fx-overflow: hidden; +} + +.two-line-list-item > .first-line > .title-pane > .title { -fx-text-fill: #292929; -fx-font-size: 15px; -fx-padding: 0 8 0 0; } -.two-line-list-item > HBox > .subtitle { - -fx-text-fill: rgba(0, 0, 0, 0.5); +.two-line-list-item > .first-line > .tag-pane > HBox > .tag { + -fx-text-fill: -fx-base-color; + -fx-background-color: -fx-base-rippler-color; + -fx-padding: 2; -fx-font-weight: normal; -fx-font-size: 12px; } -.two-line-list-item > .first-line > .tag { - -fx-text-fill: -fx-base-color; - -fx-background-color: -fx-base-rippler-color; - -fx-padding: 2; +.two-line-list-item > Pane > .subtitle { + -fx-text-fill: rgba(0, 0, 0, 0.5); -fx-font-weight: normal; -fx-font-size: 12px; } @@ -285,13 +289,13 @@ } -.two-line-item-second-large > .first-line > .title, .two-line-item-second-large-title { +.two-line-item-second-large > .first-line > .title-pane > .title, .two-line-item-second-large-title { -fx-text-fill: rgba(0, 0, 0, 0.5); -fx-font-weight: normal; -fx-font-size: 12px; } -.two-line-item-second-large > HBox > .subtitle { +.two-line-item-second-large > Pane > .subtitle { -fx-text-fill: #292929; -fx-font-size: 15px; } @@ -306,8 +310,8 @@ -fx-text-fill: white; } -.bubble > HBox > .two-line-list-item > .first-line > .title, -.bubble > HBox > .two-line-list-item > HBox > .subtitle { +.bubble > HBox > .two-line-list-item > .first-line > .title-pane > .title, +.bubble > HBox > .two-line-list-item > Pane > .subtitle { -fx-text-fill: white; } From 20b0691d8690d6ffcbaafb2674bc31768dafbde8 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 1 Aug 2023 19:42:28 +0800 Subject: [PATCH 018/104] Render the mod loader supported by this mod file on the ModListPage --- .../hmcl/ui/versions/ModListPageSkin.java | 10 +++++---- .../org/jackhuang/hmcl/mod/ModLoaderType.java | 22 ++++++++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index d7a2d25372..5d37d74763 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -450,10 +450,12 @@ final class ModInfoListCell extends MDListCell { protected void updateControl(ModInfoObject dataItem, boolean empty) { if (empty) return; content.setTitle(dataItem.getTitle()); - if (dataItem.getMod() != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA) { - content.getTags().setAll(dataItem.getMod().getDisplayName()); - } else { - content.getTags().clear(); + content.getTags().clear(); + if (dataItem.getMod() != null) { + content.getTags().add(dataItem.getModInfo().getModLoaderType().getLoaderName()); + if (I18n.getCurrentLocale().getLocale() == Locale.CHINA) { + content.getTags().add(dataItem.getMod().getDisplayName()); + } } content.setSubtitle(dataItem.getSubtitle()); if (booleanProperty != null) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java index e36d781369..f4fcf4d39a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java @@ -18,10 +18,20 @@ package org.jackhuang.hmcl.mod; public enum ModLoaderType { - UNKNOWN, - FORGE, - FABRIC, - QUILT, - LITE_LOADER, - PACK + UNKNOWN("Unknown"), + FORGE("Forge"), + FABRIC("Fabric"), + QUILT("Quilt"), + LITE_LOADER("LiteLoader"), + PACK("Pack"); + + private final String loaderName; + + ModLoaderType(String loaderName) { + this.loaderName = loaderName; + } + + public final String getLoaderName() { + return loaderName; + } } From 61a91fa44eb9bb13cbf991823b4180d02550bcc6 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 2 Aug 2023 09:57:50 +0800 Subject: [PATCH 019/104] Fix chinese searching and curseforge searching --- .../game/LocalizedRemoteModRepository.java | 74 +++++++++++++------ .../hmcl/ui/construct/TwoLineListItem.java | 2 +- .../curse/CurseForgeRemoteModRepository.java | 42 +++++++---- 3 files changed, 83 insertions(+), 35 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java index 74903aeca8..00704faec7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java @@ -21,44 +21,76 @@ import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.ui.versions.ModTranslations; +import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Stream; public abstract class LocalizedRemoteModRepository implements RemoteModRepository { + private static final int CONTAIN_CHINESE_WEIGHT = 10; protected abstract RemoteModRepository getBackedRemoteModRepository(); @Override public SearchResult search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException { - String newSearchFilter; - if (StringUtils.containsChinese(searchFilter)) { - ModTranslations modTranslations = ModTranslations.getTranslationsByRepositoryType(getType()); - List mods = modTranslations.searchMod(searchFilter); - List searchFilters = new ArrayList<>(); - int count = 0; - for (ModTranslations.Mod mod : mods) { - String englishName = mod.getName(); - if (StringUtils.isNotBlank(mod.getSubname())) { - englishName = mod.getSubname(); - } + if (!StringUtils.containsChinese(searchFilter)) { + return getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder); + } - searchFilters.add(englishName); + Set englishSearchFiltersSet = new LinkedHashSet<>(); + + int count = 0; + for (ModTranslations.Mod mod : ModTranslations.getTranslationsByRepositoryType(getType()).searchMod(searchFilter)) { + for (String englishWord : StringUtils.tokenize(StringUtils.isNotBlank(mod.getSubname()) ? mod.getSubname() : mod.getName())) { + if (englishSearchFiltersSet.contains(englishWord)) { + continue; + } - count++; - if (count >= 3) break; + englishSearchFiltersSet.add(englishWord); } - newSearchFilter = String.join(" ", searchFilters); - } else { - newSearchFilter = searchFilter; + + count++; + if (count >= 3) break; + } + + SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), sort, sortOrder); + Set searchFilterLetters = new HashSet<>(); + for (int i = 0; i < searchFilter.length(); i++) { + searchFilterLetters.add(searchFilter.subSequence(i, i + 1)); } + return new SearchResult(searchResult.getResults().map(remoteMod -> { + ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); + if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { + return Pair.pair(remoteMod, Integer.MAX_VALUE); + } + String chineseRemoteModName = chineseRemoteMod.getName(); + if (searchFilter.length() == 0 || chineseRemoteModName.length() == 0) { + return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length())); + } + int[][] lev = new int[searchFilter.length() + 1][chineseRemoteModName.length() + 1]; + for (int i = 0; i < chineseRemoteModName.length() + 1; i++) { + lev[0][i] = i; + } + for (int i = 0; i < searchFilter.length() + 1; i++) { + lev[i][0] = i; + } + for (int i = 1; i < searchFilter.length() + 1; i++) { + for (int j = 1; j < chineseRemoteModName.length() + 1; j++) { + int countByInsert = lev[i][j - 1] + 1; + int countByDel = lev[i - 1][j] + 1; + int countByReplace = searchFilter.charAt(i - 1) == chineseRemoteModName.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; + lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); + } + } - return getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort, sortOrder); + return Pair.pair( + remoteMod, + lev[searchFilter.length()][chineseRemoteModName.length()] - (searchFilterLetters.stream().anyMatch(chineseRemoteModName::contains) ? CONTAIN_CHINESE_WEIGHT : 0) + ); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), searchResult.getTotalPages()); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index a7ddf27fcc..15e4ee24e9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -197,7 +197,7 @@ public void reLayout() { double tagWidth = Math.min(tagBox.getWidth() / 2, tagMaxWidth); FXUtils.setLimitWidth((Pane) titleLabel.getParent(), titleWidth); - Rectangle titleClip = new Rectangle(0, 0, titleWidth - 10, titleLabel.getHeight()); + Rectangle titleClip = new Rectangle(0, 0, titleWidth - 6, titleLabel.getHeight()); titleLabel.getParent().setClip(titleClip); titleTranslatePlaying.set(titleLabel.getWidth() / 2 > titleMaxWidth); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 98c6936b56..282e942520 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -23,6 +23,7 @@ import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.util.MurmurHash2; import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.HttpRequest; import org.jackhuang.hmcl.util.io.JarUtils; import org.jetbrains.annotations.Nullable; @@ -43,6 +44,8 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository private static final String PREFIX = "https://api.curseforge.com"; private static final String apiKey = System.getProperty("hmcl.curseforge.apikey", JarUtils.getManifestAttribute("CurseForge-Api-Key", "")); + private static final int WORD_PERFECT_MATCH_WEIGHT = 50; + public static boolean isAvailable() { return !apiKey.isEmpty(); } @@ -103,37 +106,50 @@ public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Cat pair("searchFilter", searchFilter), pair("sortField", Integer.toString(toModsSearchSortField(sortType))), pair("sortOrder", toSortOrder(sortOrder)), - pair("index", Integer.toString(pageOffset)), + pair("index", Integer.toString(pageOffset * pageSize)), pair("pageSize", Integer.toString(pageSize))) .header("X-API-KEY", apiKey) .getJson(new TypeToken>>() { }.getType()); Stream res = response.getData().stream().map(CurseAddon::toMod); - if (sortType != SortType.NAME) { - return new SearchResult(res, response.pagination.totalCount); + if (sortType != SortType.NAME || searchFilter.length() == 0) { + return new SearchResult(res, (int)Math.ceil((double)response.pagination.totalCount / pageSize)); } // https://github.com/huanghongxun/HMCL/issues/1549 + String lowerCaseSearchFilter = searchFilter.toLowerCase(); + Map searchFilterWords = new HashMap<>(); + for (String s : StringUtils.tokenize(lowerCaseSearchFilter)) { + searchFilterWords.put(s, searchFilterWords.getOrDefault(s, 0) + 1); + } + return new SearchResult(res.map(remoteMod -> { - if (searchFilter.length() == 0 || remoteMod.getTitle().length() == 0) { - return pair(remoteMod, Math.max(searchFilter.length(), remoteMod.getTitle().length())); - } - int[][] lev = new int[searchFilter.length() + 1][remoteMod.getTitle().length() + 1]; - for (int i = 0; i < remoteMod.getTitle().length() + 1; i++) { + String lowerCaseResult = remoteMod.getTitle().toLowerCase(); + int[][] lev = new int[lowerCaseSearchFilter.length() + 1][lowerCaseResult.length() + 1]; + for (int i = 0; i < lowerCaseResult.length() + 1; i++) { lev[0][i] = i; } - for (int i = 0; i < searchFilter.length() + 1; i++) { + for (int i = 0; i < lowerCaseSearchFilter.length() + 1; i++) { lev[i][0] = i; } - for (int i = 1; i < searchFilter.length() + 1; i++) { - for (int j = 1; j < remoteMod.getTitle().length() + 1; j++) { + for (int i = 1; i < lowerCaseSearchFilter.length() + 1; i++) { + for (int j = 1; j < lowerCaseResult.length() + 1; j++) { int countByInsert = lev[i][j - 1] + 1; int countByDel = lev[i - 1][j] + 1; - int countByReplace = searchFilter.charAt(i - 1) == remoteMod.getTitle().charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; + int countByReplace = lowerCaseSearchFilter.charAt(i - 1) == lowerCaseResult.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); } } - return pair(remoteMod, lev[searchFilter.length()][remoteMod.getTitle().length()]); + + int diff = lev[lowerCaseSearchFilter.length()][lowerCaseResult.length()]; + + for (String s : StringUtils.tokenize(lowerCaseResult)) { + if (searchFilterWords.containsKey(s)) { + diff -= WORD_PERFECT_MATCH_WEIGHT * searchFilterWords.get(s) * s.length(); + } + } + + return pair(remoteMod, diff); }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.pagination.totalCount); } From b0322f0a476d9ac5e89274314f955e1581f23591 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 2 Aug 2023 10:35:56 +0800 Subject: [PATCH 020/104] Update I18N --- HMCL/src/main/resources/assets/lang/I18N.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 40fdb64732..a13a85c043 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -988,7 +988,7 @@ search.first_page=The first page search.previous_page=The previous page search.next_page=The next page search.last_page=The last page -search.page_n=Page No.%d, %s pages in total +search.page_n=%d / %s selector.choose=Choose selector.choose_file=Select a file diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index d09859e900..1cb9c49c7c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -857,7 +857,7 @@ search.first_page=第一頁 search.previous_page=上一頁 search.next_page=下一頁 search.last_page=最後一頁 -search.page_n=第 %d 頁,共 %s 頁 +search.page_n=%d / %s selector.choose=選擇 selector.choose_file=選擇檔案 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index b1e4f598e0..bda48fd29f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -857,7 +857,7 @@ search.first_page=第一页 search.previous_page=上一页 search.next_page=下一页 search.last_page=最后一页 -search.page_n=第 %d 页,共 %s 页 +search.page_n=%d / %s selector.choose=选择 selector.choose_file=选择文件 From ff4c0b663002980a1410635b8d0de6360c03828c Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 2 Aug 2023 11:01:06 +0800 Subject: [PATCH 021/104] Fix --- .../java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 2 +- .../main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 15e4ee24e9..98425604f2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -188,7 +188,7 @@ private static ChangeListener generateTranslateListener(TranslateTransi } } - public void reLayout() { + private void reLayout() { this.layout(); double titleMaxWidth = tags.size() == 0 ? this.getWidth() : this.getWidth() * TITLE_PART; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 3d7daf973f..320e0142b2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -400,7 +400,7 @@ private static final class ModItem extends StackPane { Label msg = new Label(i18n("download.failed.refresh")); msg.setPadding(new Insets(8)); elements.add(msg); - LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid())); + LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid()), exception); } for (Map.Entry> entry : dependencies.entrySet()) { From 8efaf9d9e7e05b5089fc1139e9dd42e712fa7774 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 2 Aug 2023 18:04:58 +0800 Subject: [PATCH 022/104] Fix --- .../java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 98425604f2..9a0f7cae44 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -35,6 +35,7 @@ import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; import javafx.util.Duration; @@ -169,6 +170,7 @@ public TwoLineListItem() { this.setClip(mainClip); this.minWidthProperty().set(0); + HBox.setHgrow(this, Priority.SOMETIMES); } private static ChangeListener generateTranslateListener(TranslateTransition translateTransition) { From f681f714250bea0f5afa47759539cc1fa07c9e7f Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 2 Aug 2023 19:46:57 +0800 Subject: [PATCH 023/104] Select the specific game version when clicking the 'download' button on ModListPage --- .../java/org/jackhuang/hmcl/ui/download/DownloadPage.java | 3 ++- .../java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java | 4 ++++ .../main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index 8b6619a154..d7d8218944 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -236,8 +236,9 @@ public void showModpackDownloads() { tab.select(modpackTab); } - public void showModDownloads() { + public DownloadListPage showModDownloads() { tab.select(modTab); + return modTab.getNode(); } public void showWorldDownloads() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index f259b0a8e5..6514e84c5a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -156,6 +156,10 @@ public void setLoading(boolean loading) { this.loading.set(loading); } + public void selectVersion(String versionID) { + FXUtils.runInFX(() -> selectedVersion.set(versionID)); + } + public void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort) { retrySearch = null; setLoading(true); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index 8a220c8712..e2c6decaf8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -210,7 +210,7 @@ public void checkUpdates() { } public void download() { - Controllers.getDownloadPage().showModDownloads(); + Controllers.getDownloadPage().showModDownloads().selectVersion(versionId); Controllers.navigate(Controllers.getDownloadPage()); } From 7c62bf464157fbaea5627dfed770327c22db924d Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Mon, 7 Aug 2023 19:00:47 +0800 Subject: [PATCH 024/104] Support HMCL to update mod_data and mod_pack data from https://github.com/huanghongxun/HMCL/raw/javafx/data-json/dynamic-remote-resources.json --- HMCL/build.gradle.kts | 47 +++- HMCL/parse_mcmod_data.py | 2 +- .../java/org/jackhuang/hmcl/Launcher.java | 9 +- .../java/org/jackhuang/hmcl/Metadata.java | 3 +- .../org/jackhuang/hmcl/ui/CrashWindow.java | 2 +- .../org/jackhuang/hmcl/ui/UpgradeDialog.java | 2 +- .../hmcl/ui/account/CreateAccountPane.java | 2 +- .../org/jackhuang/hmcl/ui/main/MainPage.java | 6 +- .../org/jackhuang/hmcl/ui/main/RootPage.java | 2 +- .../jackhuang/hmcl/ui/main/SettingsPage.java | 8 +- .../hmcl/ui/versions/ModTranslations.java | 41 ++-- .../{ => hmcl}/ExecutableHeaderHelper.java | 2 +- .../upgrade/{ => hmcl}/HMCLDownloadTask.java | 2 +- .../upgrade/{ => hmcl}/IntegrityChecker.java | 2 +- .../upgrade/{ => hmcl}/RemoteVersion.java | 2 +- .../upgrade/{ => hmcl}/UpdateChannel.java | 2 +- .../upgrade/{ => hmcl}/UpdateChecker.java | 4 +- .../upgrade/{ => hmcl}/UpdateHandler.java | 2 +- .../resource/RemoteResourceManager.java | 209 ++++++++++++++++++ .../jackhuang/hmcl/util/CrashReporter.java | 4 +- .../hmcl/util/ResourceNotFoundError.java | 2 +- README.md | 28 +-- data-json/dynamic-remote-resources.json | 18 ++ 23 files changed, 346 insertions(+), 55 deletions(-) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{ => hmcl}/ExecutableHeaderHelper.java (99%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{ => hmcl}/HMCLDownloadTask.java (98%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{ => hmcl}/IntegrityChecker.java (99%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{ => hmcl}/RemoteVersion.java (98%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{ => hmcl}/UpdateChannel.java (96%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{ => hmcl}/UpdateChecker.java (97%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{ => hmcl}/UpdateHandler.java (99%) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java create mode 100644 data-json/dynamic-remote-resources.json diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 66e5992993..1a38f73f57 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -1,3 +1,6 @@ +import com.google.gson.Gson +import com.google.gson.JsonElement +import com.google.gson.JsonObject import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files @@ -7,12 +10,23 @@ import java.security.Signature import java.security.spec.PKCS8EncodedKeySpec import java.util.zip.ZipFile +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath("com.google.code.gson:gson:2.10.1") + } +} + plugins { id("com.github.johnrengelman.shadow") version "7.1.2" } val isOfficial = System.getenv("HMCL_SIGNATURE_KEY") != null - || (System.getenv("GITHUB_REPOSITORY_OWNER") == "huanghongxun" && System.getenv("GITHUB_BASE_REF").isNullOrEmpty()) + || (System.getenv("GITHUB_REPOSITORY_OWNER") == "huanghongxun" && System.getenv("GITHUB_BASE_REF") + .isNullOrEmpty()) val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number -> val offset = System.getenv("BUILD_NUMBER_OFFSET")?.toInt() ?: 0 @@ -80,6 +94,37 @@ fun attachSignature(jar: File) { } } +tasks.getByName("compileJava") { + dependsOn(tasks.create("checkDynamicRemoteResourcesHash") { + doLast { + Files.newInputStream(rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources.json")) + .use { fis -> + (Gson().fromJson( + String(fis.readAllBytes(), Charsets.UTF_8), + JsonElement::class.java + ) as JsonObject).asMap().forEach { (namespace, namespaceData) -> + (namespaceData as JsonObject).asMap().forEach { (name, nameData) -> + (nameData as JsonObject).asMap().forEach { (version, versionData) -> + val url = ((versionData as JsonObject).get("url") as com.google.gson.JsonPrimitive).asString + val localPath = (versionData.get("local_path") as com.google.gson.JsonPrimitive).asString + val sha1 = (versionData.get("sha1") as com.google.gson.JsonPrimitive).asString + + val currentSha1 = digest( + "SHA-1", + Files.readAllBytes(rootProject.rootDir.toPath().resolve(localPath)) + ).joinToString(separator = "") { "%02x".format(it) } + + if (!sha1.equals(currentSha1, ignoreCase = true)) { + throw IllegalStateException("Mismatched SHA-1 in $.${namespace}.${name}.${version} of dynamic remote resources detected. Require ${currentSha1}, but found ${sha1}") + } + } + } + } + } + } + }) +} + val java11 = sourceSets.create("java11") { java { srcDir("src/main/java11") diff --git a/HMCL/parse_mcmod_data.py b/HMCL/parse_mcmod_data.py index 27dc7424d8..3753893182 100644 --- a/HMCL/parse_mcmod_data.py +++ b/HMCL/parse_mcmod_data.py @@ -89,7 +89,7 @@ def parseMcbbs(url): if __name__ == '__main__': - json_name = sys.argv[1] or 'data.json' + json_name = sys.argv[1] or 'dynamic-remote-resources.json' with codecs.open(json_name, mode='r', encoding='utf-8-sig') as jsonfile, codecs.open('data.csv', mode='w', encoding='utf-8') as outfile: data = json.load(jsonfile) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java index 345528b816..bed9714f2e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java @@ -33,8 +33,9 @@ import org.jackhuang.hmcl.task.AsyncTaskExecutor; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.upgrade.UpdateChecker; -import org.jackhuang.hmcl.upgrade.UpdateHandler; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler; +import org.jackhuang.hmcl.upgrade.resource.RemoteResourceManager; import org.jackhuang.hmcl.util.CrashReporter; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; @@ -121,6 +122,10 @@ public void start(Stage primaryStage) { UpdateChecker.init(); + RemoteResourceManager.init(); + + RemoteResourceManager.register(); + primaryStage.show(); }); } catch (Throwable e) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 8abd681ac7..048cbf5063 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -37,7 +37,8 @@ private Metadata() {} public static final String TITLE = NAME + " " + VERSION; public static final String FULL_TITLE = FULL_NAME + " v" + VERSION; - public static final String UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link"); + public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.hmcl_update_source.override", System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link")); + public static final String RESOURCE_UPDATE_URL = System.getProperty("hmcl.resource_update_source.override", "https://github.com/huanghongxun/HMCL/raw/javafx/data-json/dynamic-remote-resources.json"); public static final String CONTACT_URL = "https://docs.hmcl.net/help.html"; public static final String HELP_URL = "https://docs.hmcl.net"; public static final String CHANGELOG_URL = "https://docs.hmcl.net/changelog/"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java index 46ce19abc0..32675ac489 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java @@ -27,7 +27,7 @@ import javafx.scene.layout.StackPane; import javafx.stage.Stage; import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.upgrade.UpdateChecker; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; import static org.jackhuang.hmcl.ui.FXUtils.newImage; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java index b43133a1cf..733e393da9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java @@ -25,7 +25,7 @@ import javafx.scene.web.WebView; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; -import org.jackhuang.hmcl.upgrade.RemoteVersion; +import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion; import java.util.logging.Level; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index 08e12ea714..2c8da1a8c1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -58,7 +58,7 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.*; -import org.jackhuang.hmcl.upgrade.IntegrityChecker; +import org.jackhuang.hmcl.upgrade.hmcl.IntegrityChecker; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; import org.jackhuang.hmcl.util.javafx.BindingMapping; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 7278fc49c5..01a38378f5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -54,9 +54,9 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.versions.GameItem; import org.jackhuang.hmcl.ui.versions.Versions; -import org.jackhuang.hmcl.upgrade.RemoteVersion; -import org.jackhuang.hmcl.upgrade.UpdateChecker; -import org.jackhuang.hmcl.upgrade.UpdateHandler; +import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.MappedObservableList; import org.jackhuang.hmcl.util.platform.JavaVersion; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 6298fb3ee5..1b861ec55a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -41,7 +41,7 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem; import org.jackhuang.hmcl.ui.versions.Versions; -import org.jackhuang.hmcl.upgrade.UpdateChecker; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.versioning.VersionNumber; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index b534709448..a76070c1c6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -27,10 +27,10 @@ import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; -import org.jackhuang.hmcl.upgrade.RemoteVersion; -import org.jackhuang.hmcl.upgrade.UpdateChannel; -import org.jackhuang.hmcl.upgrade.UpdateChecker; -import org.jackhuang.hmcl.upgrade.UpdateHandler; +import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateChannel; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.i18n.Locales; import org.jackhuang.hmcl.util.io.FileUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java index 8875f2f243..7d7a22cb4d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java @@ -18,11 +18,13 @@ package org.jackhuang.hmcl.ui.versions; import org.jackhuang.hmcl.mod.RemoteModRepository; +import org.jackhuang.hmcl.upgrade.resource.RemoteResourceManager; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.IOUtils; import org.jetbrains.annotations.Nullable; +import java.io.InputStream; import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; @@ -36,19 +38,19 @@ * @see mcmod.cn */ public enum ModTranslations { - MOD("/assets/mod_data.txt") { + MOD("/assets/mod_data.txt", "translation", "mod_data", "1") { @Override public String getMcmodUrl(Mod mod) { return String.format("https://www.mcmod.cn/class/%s.html", mod.getMcmod()); } }, - MODPACK("/assets/modpack_data.txt") { + MODPACK("/assets/modpack_data.txt", "translation", "modpack_data", "1") { @Override public String getMcmodUrl(Mod mod) { return String.format("https://www.mcmod.cn/modpack/%s.html", mod.getMcmod()); } }, - EMPTY("") { + EMPTY("", "", "", "") { @Override public String getMcmodUrl(Mod mod) { return ""; @@ -66,15 +68,18 @@ public static ModTranslations getTranslationsByRepositoryType(RemoteModRepositor } } - private final String resourceName; + private final String defaultResourceName; + private final RemoteResourceManager.RemoteResourceKey remoteResourceKey; private List mods; private Map modIdMap; // mod id -> mod private Map curseForgeMap; // curseforge id -> mod private List> keywords; private int maxKeywordLength = -1; - ModTranslations(String resourceName) { - this.resourceName = resourceName; + ModTranslations(String defaultResourceName, String namespace, String name, String version) { + this.defaultResourceName = defaultResourceName; + + remoteResourceKey = RemoteResourceManager.get(namespace, name, version, () -> ModTranslations.class.getResourceAsStream(defaultResourceName)); } @Nullable @@ -96,9 +101,9 @@ public Mod getModById(String id) { public List searchMod(String query) { if (!loadKeywords()) return Collections.emptyList(); - StringBuilder newQuery = query.chars() + StringBuilder newQuery = ((CharSequence) query).chars() .filter(ch -> !Character.isSpaceChar(ch)) - .collect(StringBuilder::new, (sb, value) -> sb.append((char)value), StringBuilder::append); + .collect(StringBuilder::new, (sb, value) -> sb.append((char) value), StringBuilder::append); query = newQuery.toString(); StringUtils.LongestCommonSubsequence lcs = new StringUtils.LongestCommonSubsequence(query.length(), maxKeywordLength); @@ -115,18 +120,24 @@ public List searchMod(String query) { .collect(Collectors.toList()); } - private boolean loadFromResource() { + private boolean loaded() { if (mods != null) return true; - if (StringUtils.isBlank(resourceName)) { + if (StringUtils.isBlank(defaultResourceName)) { mods = Collections.emptyList(); return true; } + try { - String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream(resourceName)); + InputStream inputStream = remoteResourceKey.getResource(); + if (inputStream == null) { + return false; + } + + String modData = IOUtils.readFullyAsString(inputStream); mods = Arrays.stream(modData.split("\n")).filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList()); return true; } catch (Exception e) { - LOG.log(Level.WARNING, "Failed to load " + resourceName, e); + LOG.log(Level.WARNING, "Failed to load " + defaultResourceName, e); return false; } } @@ -137,7 +148,7 @@ private boolean loadCurseForgeMap() { } if (mods == null) { - if (!loadFromResource()) return false; + if (!loaded()) return false; } curseForgeMap = new HashMap<>(); @@ -155,7 +166,7 @@ private boolean loadModIdMap() { } if (mods == null) { - if (!loadFromResource()) return false; + if (!loaded()) return false; } modIdMap = new HashMap<>(); @@ -175,7 +186,7 @@ private boolean loadKeywords() { } if (mods == null) { - if (!loadFromResource()) return false; + if (!loaded()) return false; } keywords = new ArrayList<>(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/ExecutableHeaderHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/ExecutableHeaderHelper.java similarity index 99% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/ExecutableHeaderHelper.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/ExecutableHeaderHelper.java index 7c47f586d8..e5c9ccb5f5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/ExecutableHeaderHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/ExecutableHeaderHelper.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade; +package org.jackhuang.hmcl.upgrade.hmcl; import java.io.IOException; import java.io.InputStream; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/HMCLDownloadTask.java similarity index 98% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/HMCLDownloadTask.java index 8b6fdc06c3..0ae8358a52 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/HMCLDownloadTask.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade; +package org.jackhuang.hmcl.upgrade.hmcl; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.util.Pack200Utils; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/IntegrityChecker.java similarity index 99% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/IntegrityChecker.java index 5faaaf8c1d..b7c3e26aed 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/IntegrityChecker.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade; +package org.jackhuang.hmcl.upgrade.hmcl; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.util.DigestUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/RemoteVersion.java similarity index 98% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/RemoteVersion.java index c3ac2caaf7..776881aca9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/RemoteVersion.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade; +package org.jackhuang.hmcl.upgrade.hmcl; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChannel.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChannel.java similarity index 96% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChannel.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChannel.java index 998a3da7d2..f56d645dd3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChannel.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChannel.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade; +package org.jackhuang.hmcl.upgrade.hmcl; import org.jackhuang.hmcl.Metadata; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChecker.java similarity index 97% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChecker.java index cc7ce8f2e8..d8d3fc7085 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChecker.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade; +package org.jackhuang.hmcl.upgrade.hmcl; import javafx.application.Platform; import javafx.beans.binding.Bindings; @@ -85,7 +85,7 @@ private static RemoteVersion checkUpdate(UpdateChannel channel) throws IOExcepti throw new IOException("Self verification failed"); } - String url = NetworkUtils.withQuery(Metadata.UPDATE_URL, mapOf( + String url = NetworkUtils.withQuery(Metadata.HMCL_UPDATE_URL, mapOf( pair("version", Metadata.VERSION), pair("channel", channel.channelName))); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateHandler.java similarity index 99% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateHandler.java index 4cd06ef8c4..41a5316a52 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateHandler.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade; +package org.jackhuang.hmcl.upgrade.hmcl; import com.google.gson.Gson; import com.google.gson.JsonParseException; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java new file mode 100644 index 0000000000..07d8aff2f3 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java @@ -0,0 +1,209 @@ +package org.jackhuang.hmcl.upgrade.resource; + +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import javafx.collections.FXCollections; +import org.jackhuang.hmcl.Metadata; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.versions.ModTranslations; +import org.jackhuang.hmcl.util.DigestUtils; +import org.jackhuang.hmcl.util.function.ExceptionalSupplier; +import org.jackhuang.hmcl.util.io.HttpRequest; +import org.jackhuang.hmcl.util.io.IOUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public final class RemoteResourceManager { + private RemoteResourceManager() { + } + + private static final class RemoteResource { + @SerializedName("sha1") + private final String sha1; + + @SerializedName("url") + private final String url; + + private transient byte[] data = null; + + private RemoteResource(String sha1, String url) { + this.sha1 = sha1; + this.url = url; + } + + public String getSha1() { + return this.sha1; + } + + public String getUrl() { + return this.url; + } + + public byte @Nullable [] getData() { + return this.data; + } + + public void download(Path path, Runnable callback) throws IOException { + if (data != null) { + return; + } + + new FileDownloadTask(new URL(url), path.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", sha1)) + .whenComplete(Schedulers.defaultScheduler(), (result, exception) -> { + if (exception != null) { + data = Files.readAllBytes(path); + callback.run(); + } + }).start(); + } + } + + public static final class RemoteResourceKey { + private final String namespace; + private final String name; + private final String version; + private final Path cachePath; + private final ExceptionalSupplier localResourceSupplier; + private String localResourceSha1 = null; + + public RemoteResourceKey(String namespace, String name, String version, ExceptionalSupplier localResourceSupplier) { + this.namespace = namespace; + this.name = name; + this.version = version; + this.localResourceSupplier = localResourceSupplier; + + this.cachePath = Metadata.HMCL_DIRECTORY.resolve("remoteResources").resolve(namespace).resolve(name).resolve(version).resolve(String.format("%s-%s-%s.resource", namespace, name, version)); + } + + private InputStream getLocalResource() throws IOException { + if (Files.isReadable(cachePath)) { + return Files.newInputStream(cachePath); + } + return localResourceSupplier.get(); + } + + private String getLocalResourceSha1() throws IOException { + if (localResourceSha1 == null) { + localResourceSha1 = DigestUtils.digestToString("SHA-1", IOUtils.readFullyAsByteArray(getLocalResource())); + } + + return localResourceSha1; + } + + @Nullable + private RemoteResource getRemoteResource() { + return Optional.ofNullable(remoteResources.get(namespace)).map(map -> map.get(name)).map(map -> map.get(version)).orElse(null); + } + + @Nullable + public InputStream getResource() throws IOException { + RemoteResource remoteResource = getRemoteResource(); + + if (remoteResource == null) { + return getLocalResource(); + } + + if (remoteResource.getSha1().equals(getLocalResourceSha1())) { + return getLocalResource(); + } + + if (remoteResource.getData() == null) { + return null; + } + + return new ByteArrayInputStream(remoteResource.getData()); + } + + public void downloadRemoteResourceIfNecessary() throws IOException { + RemoteResource remoteResource = getRemoteResource(); + + if (remoteResource == null) { + return; + } + + if (remoteResource.getSha1().equals(getLocalResourceSha1())) { + return; + } + + remoteResource.download(cachePath, () -> localResourceSha1 = null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RemoteResourceKey that = (RemoteResourceKey) o; + + if (!namespace.equals(that.namespace)) return false; + if (!name.equals(that.name)) return false; + return version.equals(that.version); + } + + @Override + public int hashCode() { + int result = namespace.hashCode(); + result = 31 * result + name.hashCode(); + result = 31 * result + version.hashCode(); + return result; + } + } + + private static final Map>> remoteResources = FXCollections.observableMap(new HashMap<>()); + private static boolean fetching = false; + + private static final Map keys = new HashMap<>(); + + public static void init() { + if (remoteResources.size() != 0) { + return; + } + + synchronized (RemoteResourceManager.class) { + if (fetching) { + return; + } + fetching = true; + + Task.>>>supplyAsync(() -> HttpRequest.GET(Metadata.RESOURCE_UPDATE_URL).getJson( + new TypeToken>>>() { + }.getType()) + ).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> { + if (exception == null) { + remoteResources.clear(); + remoteResources.putAll(result); + + for (RemoteResourceKey key : keys.values()) { + key.downloadRemoteResourceIfNecessary(); + } + } + + fetching = false; + }).start(); + } + } + + public static void register() { + ModTranslations.values(); + } + + public static RemoteResourceKey get(@NotNull String namespace, @NotNull String name, @NotNull String version, ExceptionalSupplier defaultSupplier) { + String stringKey = String.format("%s:%s:%s", namespace, name, version); + RemoteResourceKey key = keys.containsKey(stringKey) ? keys.get(stringKey) : new RemoteResourceKey(namespace, name, version, defaultSupplier); + Task.runAsync(key::downloadRemoteResourceIfNecessary).start(); + keys.put(stringKey, key); + return key; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java index 5ec3d2e311..8d71a256a5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java @@ -23,8 +23,8 @@ import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.countly.CrashReport; import org.jackhuang.hmcl.ui.CrashWindow; -import org.jackhuang.hmcl.upgrade.IntegrityChecker; -import org.jackhuang.hmcl.upgrade.UpdateChecker; +import org.jackhuang.hmcl.upgrade.hmcl.IntegrityChecker; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.IOException; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java index d404c0fcdd..1977eeb81b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java @@ -35,7 +35,7 @@ public ResourceNotFoundError(String message, Throwable cause) { public static InputStream getResourceAsStream(String url) { InputStream stream = ResourceNotFoundError.class.getResourceAsStream(url); if (stream == null) - throw new ResourceNotFoundError("Resource not found: " + url); + throw new ResourceNotFoundError("RemoteResource not found: " + url); return stream; } } diff --git a/README.md b/README.md index 0c7d63fc86..f934c06b68 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,18 @@ Simply execute the following command in project root directory: Make sure you have Java installed with JavaFX 8 at least. Liberica Full JDK 8 or later is recommended. ## JVM Options (for debugging) -| Parameter | Description | -|----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| -| `-Dhmcl.home=` | Override HMCL directory. | -| `-Dhmcl.self_integrity_check.disable=true` | Bypass the self integrity check when checking for update. | -| `-Dhmcl.bmclapi.override=` | Override API Root of BMCLAPI download provider, defaults to `https://bmclapi2.bangbang93.com`. e.g. `https://download.mcbbs.net`. | -| `-Dhmcl.font.override=` | Override font family. | -| `-Dhmcl.version.override=` | Override the version number. | -| `-Dhmcl.update_source.override=` | Override the update source. | -| `-Dhmcl.authlibinjector.location=` | Use specified authlib-injector (instead of downloading one). | -| `-Dhmcl.openjfx.repo=` | Add custom Maven repository for download OpenJFX. | -| `-Dhmcl.native.encoding=` | Override the native encoding. | -| `-Dhmcl.microsoft.auth.id=` | Override Microsoft OAuth App ID. | -| `-Dhmcl.microsoft.auth.secret=` | Override Microsoft OAuth App secret. | +| Parameter | Description | +|------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| `-Dhmcl.home=` | Override HMCL directory. | +| `-Dhmcl.self_integrity_check.disable=true` | Bypass the self integrity check when checking for update. | +| `-Dhmcl.bmclapi.override=` | Override API Root of BMCLAPI download provider, defaults to `https://bmclapi2.bangbang93.com`. e.g. `https://download.mcbbs.net`. | +| `-Dhmcl.font.override=` | Override font family. | +| `-Dhmcl.version.override=` | Override the version number. | +| ~~`-Dhmcl.update_source.override=`~~ | Override the update source for HMCL itself. (Deprecated, please use `hmcl.hmcl_update_source.override` instead.) | +| `-Dhmcl.hmcl_update_source.override=` | Override the update source for HMCL itself. | +| `-Dhmcl.resource_update_source.override=` | Override the update source for dynamic remote resources. | +| `-Dhmcl.authlibinjector.location=` | Use specified authlib-injector (instead of downloading one). | +| `-Dhmcl.openjfx.repo=` | Add custom Maven repository for download OpenJFX. | +| `-Dhmcl.native.encoding=` | Override the native encoding. | +| `-Dhmcl.microsoft.auth.id=` | Override Microsoft OAuth App ID. | +| `-Dhmcl.microsoft.auth.secret=` | Override Microsoft OAuth App secret. | diff --git a/data-json/dynamic-remote-resources.json b/data-json/dynamic-remote-resources.json new file mode 100644 index 0000000000..bb0e55c236 --- /dev/null +++ b/data-json/dynamic-remote-resources.json @@ -0,0 +1,18 @@ +{ + "translation": { + "mod_data": { + "1": { + "url": "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", + "local_path": "HMCL/src/main/resources/assets/mod_data.txt", + "sha1": "0ae36a65a00b00176358bd6b0d3c8787b3668c23" + } + }, + "modpack_data": { + "1": { + "url": "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", + "local_path": "HMCL/src/main/resources/assets/modpack_data.txt", + "sha1": "b0e771db170835e1154da4c21b7417a688836162" + } + } + } +} \ No newline at end of file From caf9cb294ed72419937bd1cd1275bd194cd548e8 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 8 Aug 2023 10:50:35 +0800 Subject: [PATCH 025/104] Enhance :HMCL:build.gradle.kts --- HMCL/build.gradle.kts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 1a38f73f57..fbbbb3bd3e 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -8,6 +8,7 @@ import java.security.KeyFactory import java.security.MessageDigest import java.security.Signature import java.security.spec.PKCS8EncodedKeySpec +import java.util.Locale import java.util.zip.ZipFile buildscript { @@ -33,7 +34,7 @@ val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number -> if (number != null) { (number - offset).toString() } else { - val shortCommit = System.getenv("GITHUB_SHA")?.toLowerCase()?.substring(0, 7) + val shortCommit = System.getenv("GITHUB_SHA")?.lowercase(Locale.ENGLISH)?.substring(0, 7) val prefix = if (isOfficial) "dev" else "unofficial" if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT" } @@ -105,7 +106,7 @@ tasks.getByName("compileJava") { ) as JsonObject).asMap().forEach { (namespace, namespaceData) -> (namespaceData as JsonObject).asMap().forEach { (name, nameData) -> (nameData as JsonObject).asMap().forEach { (version, versionData) -> - val url = ((versionData as JsonObject).get("url") as com.google.gson.JsonPrimitive).asString + require(versionData is JsonObject) val localPath = (versionData.get("local_path") as com.google.gson.JsonPrimitive).asString val sha1 = (versionData.get("sha1") as com.google.gson.JsonPrimitive).asString @@ -115,7 +116,7 @@ tasks.getByName("compileJava") { ).joinToString(separator = "") { "%02x".format(it) } if (!sha1.equals(currentSha1, ignoreCase = true)) { - throw IllegalStateException("Mismatched SHA-1 in $.${namespace}.${name}.${version} of dynamic remote resources detected. Require ${currentSha1}, but found ${sha1}") + throw IllegalStateException("Mismatched SHA-1 in $.${namespace}.${name}.${version} of dynamic remote resources detected. Require ${currentSha1}, but found $sha1") } } } From e27c5dc6cf13667ca5da650f7db2fb2d4269eed1 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 8 Aug 2023 11:40:14 +0800 Subject: [PATCH 026/104] Revert parse_mcmod_data.py --- HMCL/parse_mcmod_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/parse_mcmod_data.py b/HMCL/parse_mcmod_data.py index 3753893182..27dc7424d8 100644 --- a/HMCL/parse_mcmod_data.py +++ b/HMCL/parse_mcmod_data.py @@ -89,7 +89,7 @@ def parseMcbbs(url): if __name__ == '__main__': - json_name = sys.argv[1] or 'dynamic-remote-resources.json' + json_name = sys.argv[1] or 'data.json' with codecs.open(json_name, mode='r', encoding='utf-8-sig') as jsonfile, codecs.open('data.csv', mode='w', encoding='utf-8') as outfile: data = json.load(jsonfile) From 545c4f29d864ab29aaa52378e411d84a552119c8 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 8 Aug 2023 17:25:42 +0800 Subject: [PATCH 027/104] Abstract 'new Image' to FXUtils.newBuiltinImage and FXUtils.newRemoteImage FXUtils.newBuiltinImage is used to load image which is supposed to be correct definitely and is a file within the jar. Or, it will throw ResourceNotFoundError. FXUtils.newRemoteImage is used to load image from the internet. It will cache the data of images for the further usage. The cached data will be deleted when HMCL is closed or hidden. --- .../mickey/minecraft/skin/fx/SkinCanvas.java | 5 +- .../hmcl/game/HMCLGameRepository.java | 20 ++--- .../org/jackhuang/hmcl/ui/Controllers.java | 11 ++- .../org/jackhuang/hmcl/ui/CrashWindow.java | 4 +- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 73 +++++++++++++++++-- .../jackhuang/hmcl/ui/GameCrashWindow.java | 4 +- .../org/jackhuang/hmcl/ui/InstallerItem.java | 2 +- .../java/org/jackhuang/hmcl/ui/LogWindow.java | 4 +- .../java/org/jackhuang/hmcl/ui/WebStage.java | 4 +- .../ui/decorator/DecoratorController.java | 6 +- .../hmcl/ui/download/VersionsPage.java | 3 +- .../org/jackhuang/hmcl/ui/main/AboutPage.java | 22 +++--- .../jackhuang/hmcl/ui/main/FeedbackPage.java | 8 +- .../org/jackhuang/hmcl/ui/main/MainPage.java | 2 +- .../hmcl/ui/versions/DownloadListPage.java | 3 +- .../hmcl/ui/versions/DownloadPage.java | 4 +- .../ui/versions/GameAdvancedListItem.java | 4 +- .../hmcl/ui/versions/ModListPageSkin.java | 2 +- .../hmcl/ui/versions/VersionIconDialog.java | 2 +- .../hmcl/ui/versions/VersionSettingsPage.java | 2 +- 20 files changed, 122 insertions(+), 63 deletions(-) diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java index 889732c20f..a527635702 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java +++ b/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java @@ -12,12 +12,13 @@ import javafx.scene.transform.Scale; import javafx.scene.transform.Translate; +import org.jackhuang.hmcl.ui.FXUtils; import org.jetbrains.annotations.Nullable; public class SkinCanvas extends Group { - public static final Image ALEX = new Image("/assets/img/skin/alex.png"); - public static final Image STEVE = new Image("/assets/img/skin/steve.png"); + public static final Image ALEX = FXUtils.newBuiltinImage("/assets/img/skin/alex.png"); + public static final Image STEVE = FXUtils.newBuiltinImage("/assets/img/skin/steve.png"); public static final SkinCube ALEX_LARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, true); public static final SkinCube ALEX_RARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, true); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 7b5aea0b75..2609468b9b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -52,7 +52,7 @@ import java.util.stream.Stream; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Pair.pair; @@ -263,7 +263,7 @@ public File getVersionIconFile(String id) { public Image getVersionIconImage(String id) { if (id == null || !isLoaded()) - return newImage("/assets/img/grass.png"); + return newBuiltinImage("/assets/img/grass.png"); VersionSetting vs = getLocalVersionSettingOrCreate(id); VersionIconType iconType = Optional.ofNullable(vs).map(VersionSetting::getVersionIcon).orElse(VersionIconType.DEFAULT); @@ -276,21 +276,21 @@ public Image getVersionIconImage(String id) { else if (LibraryAnalyzer.isModded(this, version)) { LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version); if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FABRIC)) - return newImage("/assets/img/fabric.png"); + return newBuiltinImage("/assets/img/fabric.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE)) - return newImage("/assets/img/forge.png"); + return newBuiltinImage("/assets/img/forge.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT)) - return newImage("/assets/img/quilt.png"); + return newBuiltinImage("/assets/img/quilt.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.OPTIFINE)) - return newImage("/assets/img/command.png"); + return newBuiltinImage("/assets/img/command.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.LITELOADER)) - return newImage("/assets/img/chicken.png"); + return newBuiltinImage("/assets/img/chicken.png"); else - return newImage("/assets/img/furnace.png"); + return newBuiltinImage("/assets/img/furnace.png"); } else - return newImage("/assets/img/grass.png"); + return newBuiltinImage("/assets/img/grass.png"); } else { - return newImage(iconType.getResourceUrl()); + return newBuiltinImage(iconType.getResourceUrl()); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index d4c4f9d240..ebab5df801 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -51,10 +51,7 @@ import org.jackhuang.hmcl.ui.main.RootPage; import org.jackhuang.hmcl.ui.versions.GameListPage; import org.jackhuang.hmcl.ui.versions.VersionPage; -import org.jackhuang.hmcl.util.FutureCallback; -import org.jackhuang.hmcl.util.Lazy; -import org.jackhuang.hmcl.util.Logging; -import org.jackhuang.hmcl.util.TaskCancellationAction; +import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.JavaVersion; @@ -65,7 +62,7 @@ import java.util.concurrent.CompletableFuture; import static org.jackhuang.hmcl.setting.ConfigHolder.*; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class Controllers { @@ -204,7 +201,7 @@ public static void initialize(Stage stage) { decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty()); scene.getStylesheets().setAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); - stage.getIcons().add(newImage("/assets/img/icon.png")); + stage.getIcons().add(newBuiltinImage("/assets/img/icon.png")); stage.setTitle(Metadata.FULL_TITLE); stage.initStyle(StageStyle.TRANSPARENT); stage.setScene(scene); @@ -352,5 +349,7 @@ public static void shutdown() { stage = null; scene = null; onApplicationStop(); + + FXUtils.shutdown(); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java index 32675ac489..d2cb5d93b6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java @@ -29,7 +29,7 @@ import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; /** @@ -67,7 +67,7 @@ public CrashWindow(String text) { Scene scene = new Scene(pane, 800, 480); setScene(scene); - getIcons().add(newImage("/assets/img/icon.png")); + getIcons().add(newBuiltinImage("/assets/img/icon.png")); setTitle(i18n("message.error")); setOnCloseRequest(e -> System.exit(1)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 670923cdcd..a9e14bbf51 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -46,13 +46,16 @@ import javafx.util.Callback; import javafx.util.Duration; import javafx.util.StringConverter; +import org.glavo.png.javafx.PNGJavaFXUtils; import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.construct.JFXHyperlink; import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.ResourceNotFoundError; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.javafx.ExtendedProperties; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -74,11 +77,11 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; import java.util.List; -import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; @@ -94,7 +97,21 @@ public final class FXUtils { private FXUtils() { } - public static String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace"; + public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace"; + + private static final Map imageCache = new ConcurrentHashMap<>(); + + public static synchronized void shutdown() { + for (Path path : imageCache.values()) { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + LOG.log(Level.WARNING, String.format("Failed to delete cache file %s.", path), e); + } + } + + imageCache.clear(); + } public static void runInFX(Runnable runnable) { if (Platform.isFxApplicationThread()) { @@ -449,7 +466,8 @@ public static void openLink(String link) { } catch (Throwable e) { LOG.log(Level.WARNING, "An exception occurred while calling rundll32", e); } - } if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) { + } + if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) { for (String browser : linuxBrowsers) { try (final InputStream is = Runtime.getRuntime().exec(new String[]{"which", browser}).getInputStream()) { if (is.read() != -1) { @@ -663,14 +681,55 @@ public static void unbindEnum(JFXComboBox> comboBox) { * @see org.jackhuang.hmcl.util.CrashReporter * @see ResourceNotFoundError */ - public static Image newImage(String url) { + public static Image newBuiltinImage(String url) { + return newBuiltinImage(url, 0, 0, false, false); + } + + public static Image newBuiltinImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) { try { - return new Image(url); + return new Image(url, requestedWidth, requestedHeight, preserveRatio, smooth); } catch (IllegalArgumentException e) { throw new ResourceNotFoundError("Cannot access image: " + url, e); } } + public static Image newRemoteImage(String url) { + return newRemoteImage(url, 0, 0, false, false, false); + } + + public static Image newRemoteImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth, boolean backgroundLoading) { + if (imageCache.containsKey(url)) { + Path path = imageCache.get(url); + if (Files.isReadable(path)) { + try { + return new Image(Files.newInputStream(path), requestedWidth, requestedHeight, preserveRatio, smooth); + } catch (IOException e) { + LOG.log(Level.WARNING, "An exception encountered while reading data from cached image file.", e); + } + } + + try { + Files.deleteIfExists(path); + } catch (IOException e) { + LOG.log(Level.WARNING, "An exception encountered while deleting broken cached image file.", e); + } + + imageCache.remove(url); + } + + Image image = new Image(url, requestedWidth, requestedHeight, preserveRatio, smooth, backgroundLoading); + image.progressProperty().addListener((observable, oldValue, newValue) -> { + if (newValue.doubleValue() == 1D && image.getWidth() != 0D && image.getHeight() != 0D) { + Task.runAsync(() -> { + Path path = Files.createTempFile("hmcl-net-resource-cache-", ".cache"); + PNGJavaFXUtils.writeImage(image, path); + imageCache.put(url, path); + }).start(); + } + }); + return image; + } + public static JFXButton newRaisedButton(String text) { JFXButton button = new JFXButton(text); button.getStyleClass().add("jfx-button-raised"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java index 9f6e47ff4f..9028c87120 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java @@ -64,7 +64,7 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Pair.pair; @@ -114,7 +114,7 @@ public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType e setScene(new Scene(view, 800, 480)); getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); setTitle(i18n("game.crash.title")); - getIcons().add(newImage("/assets/img/icon.png")); + getIcons().add(newBuiltinImage("/assets/img/icon.png")); analyzeCrashReport(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index 994a642124..f03e8757d9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -207,7 +207,7 @@ public static class InstallerItemSkin extends SkinBase { pane.pseudoClassStateChanged(CARD, control.style == Style.CARD); if (control.imageUrl != null) { - ImageView view = new ImageView(new Image(control.imageUrl)); + ImageView view = new ImageView(FXUtils.newRemoteImage(control.imageUrl)); Node node = FXUtils.limitingSize(view, 32, 32); node.setMouseTransparent(true); node.getStyleClass().add("installer-item-image"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java index 523b46dd48..4af43cac8f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java @@ -57,7 +57,7 @@ import java.util.stream.IntStream; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.util.Lang.thread; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.StringUtils.parseEscapeSequence; @@ -94,7 +94,7 @@ public LogWindow() { setScene(new Scene(impl, 800, 480)); getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); setTitle(i18n("logwindow.title")); - getIcons().add(newImage("/assets/img/icon.png")); + getIcons().add(newBuiltinImage("/assets/img/icon.png")); levelShownMap.values().forEach(property -> property.addListener((a, b, newValue) -> shakeLogs())); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java index 0562663850..32a413407f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java @@ -29,7 +29,7 @@ import org.jackhuang.hmcl.setting.Theme; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; public class WebStage extends Stage { protected final StackPane pane = new StackPane(); @@ -44,7 +44,7 @@ public WebStage() { public WebStage(int width, int height) { setScene(new Scene(pane, width, height)); getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); - getIcons().add(newImage("/assets/img/icon.png")); + getIcons().add(newBuiltinImage("/assets/img/icon.png")); webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile()); webView.setContextMenuEnabled(false); progressBar.progressProperty().bind(webView.getEngine().getLoadWorker().progressProperty()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 14dba47ba1..6c02002a6f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -64,7 +64,7 @@ import static java.util.logging.Level.WARNING; import static java.util.stream.Collectors.toList; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.io.FileUtils.getExtension; @@ -172,7 +172,7 @@ private Background getBackground() { image = tryLoadImage(backgroundImageUrl).orElse(null); break; case CLASSIC: - image = newImage("/assets/img/background-classic.jpg"); + image = newBuiltinImage("/assets/img/background-classic.jpg"); break; case TRANSLUCENT: return new Background(new BackgroundFill(new Color(1, 1, 1, 0.5), CornerRadii.EMPTY, Insets.EMPTY)); @@ -202,7 +202,7 @@ private Image loadDefaultBackgroundImage() { return image.orElseGet(() -> { if (defaultBackground == null) - defaultBackground = newImage("/assets/img/background.jpg"); + defaultBackground = newBuiltinImage("/assets/img/background.jpg"); return defaultBackground; }); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index f69a553a09..4c94f5a1b1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -41,6 +41,7 @@ import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; @@ -286,7 +287,7 @@ private static class RemoteVersionListCell extends ListCell { } private Image getIcon(VersionIconType type) { - return icons.computeIfAbsent(type, iconType -> new Image(iconType.getResourceUrl())); + return icons.computeIfAbsent(type, iconType -> FXUtils.newBuiltinImage(iconType.getResourceUrl())); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index 158baa165d..096d4038c5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -35,13 +35,13 @@ public AboutPage() { ComponentList about = new ComponentList(); { IconedTwoLineListItem launcher = new IconedTwoLineListItem(); - launcher.setImage(new Image("/assets/img/craft_table.png")); + launcher.setImage(FXUtils.newBuiltinImage("/assets/img/craft_table.png")); launcher.setTitle("Hello Minecraft! Launcher"); launcher.setSubtitle(Metadata.VERSION); launcher.setExternalLink("https://hmcl.huangyuhui.net"); IconedTwoLineListItem author = new IconedTwoLineListItem(); - author.setImage(new Image("/assets/img/yellow_fish.png")); + author.setImage(FXUtils.newBuiltinImage("/assets/img/yellow_fish.png")); author.setTitle("huanghongxun"); author.setSubtitle(i18n("about.author.statement")); author.setExternalLink("https://space.bilibili.com/1445341"); @@ -52,54 +52,54 @@ public AboutPage() { ComponentList thanks = new ComponentList(); { IconedTwoLineListItem yushijinhun = new IconedTwoLineListItem(); - yushijinhun.setImage(new Image("/assets/img/yushijinhun.png")); + yushijinhun.setImage(FXUtils.newBuiltinImage("/assets/img/yushijinhun.png")); yushijinhun.setTitle("yushijinhun"); yushijinhun.setSubtitle(i18n("about.thanks_to.yushijinhun.statement")); yushijinhun.setExternalLink("https://yushi.moe/"); IconedTwoLineListItem bangbang93 = new IconedTwoLineListItem(); - bangbang93.setImage(new Image("/assets/img/bangbang93.png")); + bangbang93.setImage(FXUtils.newBuiltinImage("/assets/img/bangbang93.png")); bangbang93.setTitle("bangbang93"); bangbang93.setSubtitle(i18n("about.thanks_to.bangbang93.statement")); bangbang93.setExternalLink("https://bmclapi2.bangbang93.com/"); IconedTwoLineListItem glavo = new IconedTwoLineListItem(); - glavo.setImage(new Image("/assets/img/glavo.png")); + glavo.setImage(FXUtils.newBuiltinImage("/assets/img/glavo.png")); glavo.setTitle("Glavo"); glavo.setSubtitle(i18n("about.thanks_to.glavo.statement")); glavo.setExternalLink("https://github.com/Glavo"); IconedTwoLineListItem gamerteam = new IconedTwoLineListItem(); gamerteam.setTitle("gamerteam"); - gamerteam.setImage(new Image("/assets/img/gamerteam.png")); + gamerteam.setImage(FXUtils.newBuiltinImage("/assets/img/gamerteam.png")); gamerteam.setSubtitle(i18n("about.thanks_to.gamerteam.statement")); gamerteam.setExternalLink("http://www.zhaisoul.com/"); IconedTwoLineListItem redLnn = new IconedTwoLineListItem(); redLnn.setTitle("Red_lnn"); - redLnn.setImage(new Image("/assets/img/red_lnn.png")); + redLnn.setImage(FXUtils.newBuiltinImage("/assets/img/red_lnn.png")); redLnn.setSubtitle(i18n("about.thanks_to.red_lnn.statement")); IconedTwoLineListItem mcbbs = new IconedTwoLineListItem(); - mcbbs.setImage(new Image("/assets/img/chest.png")); + mcbbs.setImage(FXUtils.newBuiltinImage("/assets/img/chest.png")); mcbbs.setTitle(i18n("about.thanks_to.mcbbs")); mcbbs.setSubtitle(i18n("about.thanks_to.mcbbs.statement")); mcbbs.setExternalLink("https://www.mcbbs.net/"); IconedTwoLineListItem mcmod = new IconedTwoLineListItem(); - mcmod.setImage(new Image("/assets/img/mcmod.png")); + mcmod.setImage(FXUtils.newBuiltinImage("/assets/img/mcmod.png")); mcmod.setTitle(i18n("about.thanks_to.mcmod")); mcmod.setSubtitle(i18n("about.thanks_to.mcmod.statement")); mcmod.setExternalLink("https://www.mcmod.cn/"); IconedTwoLineListItem contributors = new IconedTwoLineListItem(); - contributors.setImage(new Image("/assets/img/github.png")); + contributors.setImage(FXUtils.newBuiltinImage("/assets/img/github.png")); contributors.setTitle(i18n("about.thanks_to.contributors")); contributors.setSubtitle(i18n("about.thanks_to.contributors.statement")); contributors.setExternalLink("https://github.com/huanghongxun/HMCL/graphs/contributors"); IconedTwoLineListItem users = new IconedTwoLineListItem(); - users.setImage(new Image("/assets/img/craft_table.png")); + users.setImage(FXUtils.newBuiltinImage("/assets/img/craft_table.png")); users.setTitle(i18n("about.thanks_to.users")); users.setSubtitle(i18n("about.thanks_to.users.statement")); users.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java index 58972ed3fc..f022eeb003 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java @@ -43,25 +43,25 @@ public FeedbackPage() { ComponentList community = new ComponentList(); { IconedTwoLineListItem users = new IconedTwoLineListItem(); - users.setImage(new Image("/assets/img/craft_table.png")); + users.setImage(FXUtils.newBuiltinImage("/assets/img/craft_table.png")); users.setTitle(i18n("feedback.qq_group")); users.setSubtitle(i18n("feedback.qq_group.statement")); users.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor"); IconedTwoLineListItem github = new IconedTwoLineListItem(); - github.setImage(new Image("/assets/img/github.png")); + github.setImage(FXUtils.newBuiltinImage("/assets/img/github.png")); github.setTitle(i18n("feedback.github")); github.setSubtitle(i18n("feedback.github.statement")); github.setExternalLink("https://github.com/huanghongxun/HMCL/issues/new/choose"); IconedTwoLineListItem discord = new IconedTwoLineListItem(); - discord.setImage(new Image("/assets/img/discord.png")); + discord.setImage(FXUtils.newBuiltinImage("/assets/img/discord.png")); discord.setTitle(i18n("feedback.discord")); discord.setSubtitle(i18n("feedback.discord.statement")); discord.setExternalLink("https://discord.gg/jVvC7HfM6U"); IconedTwoLineListItem kookapp = new IconedTwoLineListItem(); - kookapp.setImage(new Image("/assets/img/kookapp.png")); + kookapp.setImage(FXUtils.newBuiltinImage("/assets/img/kookapp.png")); kookapp.setTitle(i18n("feedback.kookapp")); kookapp.setSubtitle(i18n("feedback.kookapp.statement")); kookapp.setExternalLink("https://kook.top/Kx7n3t"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 01a38378f5..e81f147f52 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -105,7 +105,7 @@ public final class MainPage extends StackPane implements DecoratorPage { } catch (IOException ignored) { } } else { - titleIcon.setImage(new Image("/assets/img/icon.png", 20, 20, false, false)); + titleIcon.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png", 20, 20, false, false)); } Label titleLabel = new Label(Metadata.FULL_TITLE); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 6514e84c5a..41d58e0b2a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -35,7 +35,6 @@ import javafx.scene.control.Label; import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.*; import org.jackhuang.hmcl.game.GameVersion; @@ -480,7 +479,7 @@ protected void updateControl(RemoteMod dataItem, boolean empty) { .collect(Collectors.toList())); if (StringUtils.isNotBlank(dataItem.getIconUrl())) { - imageView.setImage(new Image(dataItem.getIconUrl(), 40, 40, true, true, true)); + imageView.setImage(FXUtils.newRemoteImage(dataItem.getIconUrl(), 40, 40, true, true, true)); } } }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 320e0142b2..bc3d8e7d18 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -234,7 +234,7 @@ protected ModDownloadPageSkin(DownloadPage control) { { ImageView imageView = new ImageView(); if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) { - imageView.setImage(new Image(getSkinnable().addon.getIconUrl(), 40, 40, true, true, true)); + imageView.setImage(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 40, 40, true, true, true)); } descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40)); @@ -369,7 +369,7 @@ private static final class DependencyModItem extends StackPane { .collect(Collectors.toList())); if (StringUtils.isNotBlank(addon.getIconUrl())) { - imageView.setImage(new Image(addon.getIconUrl(), 40, 40, true, true, true)); + imageView.setImage(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true, true)); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java index 3b07ecda6c..d6a71f5260 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java @@ -30,7 +30,7 @@ import java.util.function.Consumer; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameAdvancedListItem extends AdvancedListItem { @@ -72,7 +72,7 @@ private void loadVersion(String version) { Tooltip.uninstall(this,tooltip); setTitle(i18n("version.empty")); setSubtitle(i18n("version.empty.add")); - imageView.setImage(newImage("/assets/img/grass.png")); + imageView.setImage(newBuiltinImage("/assets/img/grass.png")); tooltip.setText(""); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 5d37d74763..14e7ddab5d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -296,7 +296,7 @@ class ModInfoDialog extends JFXDialogLayout { if (stream != null) { imageView.setImage(new Image(stream, 40, 40, true, true)); } else { - imageView.setImage(new Image("/assets/img/command.png", 40, 40, true, true)); + imageView.setImage(FXUtils.newBuiltinImage("/assets/img/command.png", 40, 40, true, true)); } }).start(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index f0fb2b619c..9886e526d9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -104,7 +104,7 @@ private Node createCustomIcon() { } private Node createIcon(VersionIconType type) { - ImageView imageView = new ImageView(new Image(type.getResourceUrl())); + ImageView imageView = new ImageView(FXUtils.newBuiltinImage(type.getResourceUrl())); imageView.setMouseTransparent(true); RipplerContainer container = new RipplerContainer(imageView); FXUtils.setLimitWidth(container, 36); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index 89d116c7e3..aad1723f3a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -146,7 +146,7 @@ public VersionSettingsPage(boolean globalSetting) { rootPane.getChildren().add(iconPickerItemWrapper); iconPickerItem = new ImagePickerItem(); - iconPickerItem.setImage(new Image("/assets/img/icon.png")); + iconPickerItem.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png")); iconPickerItem.setTitle(i18n("settings.icon")); iconPickerItem.setOnSelectButtonClicked(e -> onExploreIcon()); iconPickerItem.setOnDeleteButtonClicked(e -> onDeleteIcon()); From ec50706158a5585109db0880b8bc569fc3e94be6 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 8 Aug 2023 17:31:26 +0800 Subject: [PATCH 028/104] Add javadoc for FXUtils.newBuiltinImage and FXUtils.newRemoteImage. --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index a9e14bbf51..21934c53f5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -685,6 +685,22 @@ public static Image newBuiltinImage(String url) { return newBuiltinImage(url, 0, 0, false, false); } + /** + * Suppress IllegalArgumentException since the url is supposed to be correct definitely. + * + * @param url the url of image. The image resource should be a file within the jar. + * @param requestedWidth the image's bounding box width + * @param requestedHeight the image's bounding box height + * @param preserveRatio indicates whether to preserve the aspect ratio of + * the original image when scaling to fit the image within the + * specified bounding box + * @param smooth indicates whether to use a better quality filtering + * algorithm or a faster one when scaling this image to fit within + * the specified bounding box + * @return the image resource within the jar. + * @see org.jackhuang.hmcl.util.CrashReporter + * @see ResourceNotFoundError + */ public static Image newBuiltinImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) { try { return new Image(url, requestedWidth, requestedHeight, preserveRatio, smooth); @@ -693,10 +709,36 @@ public static Image newBuiltinImage(String url, double requestedWidth, double re } } + /** + * Load image from the internet. It will cache the data of images for the further usage. + * The cached data will be deleted when HMCL is closed or hidden. + * + * @param url the url of image. The image resource should be a file on the internet. + * @return the image resource within the jar. + * @see org.jackhuang.hmcl.util.CrashReporter + * @see ResourceNotFoundError + */ public static Image newRemoteImage(String url) { return newRemoteImage(url, 0, 0, false, false, false); } + /** + * Load image from the internet. It will cache the data of images for the further usage. + * The cached data will be deleted when HMCL is closed or hidden. + * + * @param url the url of image. The image resource should be a file on the internet. + * @param requestedWidth the image's bounding box width + * @param requestedHeight the image's bounding box height + * @param preserveRatio indicates whether to preserve the aspect ratio of + * the original image when scaling to fit the image within the + * specified bounding box + * @param smooth indicates whether to use a better quality filtering + * algorithm or a faster one when scaling this image to fit within + * the specified bounding box + * @return the image resource within the jar. + * @see org.jackhuang.hmcl.util.CrashReporter + * @see ResourceNotFoundError + */ public static Image newRemoteImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth, boolean backgroundLoading) { if (imageCache.containsKey(url)) { Path path = imageCache.get(url); From ffa81cd7884f7f230613a581f52cdb7fec40115c Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 8 Aug 2023 17:37:54 +0800 Subject: [PATCH 029/104] Fix checkstyle --- HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java | 1 - HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java | 1 - HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java | 1 - HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java | 1 - .../main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java | 1 - .../java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java | 1 - .../java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java | 1 - 7 files changed, 7 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 21934c53f5..45adaef8c7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -55,7 +55,6 @@ import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.ResourceNotFoundError; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.javafx.ExtendedProperties; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.OperatingSystem; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index f03e8757d9..a203756a38 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -30,7 +30,6 @@ import javafx.scene.control.Label; import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index 096d4038c5..12d032f167 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -19,7 +19,6 @@ import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; -import javafx.scene.image.Image; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.Metadata; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java index f022eeb003..36353e050b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java @@ -19,7 +19,6 @@ import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; -import javafx.scene.image.Image; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.ComponentList; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index bc3d8e7d18..6e09c00d90 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -27,7 +27,6 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.stage.FileChooser; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index 9886e526d9..ffa87e1c9b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.versions; import javafx.scene.Node; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.FlowPane; import javafx.stage.FileChooser; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index aad1723f3a..45b8357430 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -27,7 +27,6 @@ import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; -import javafx.scene.image.Image; import javafx.scene.layout.*; import javafx.scene.text.Text; import javafx.stage.FileChooser; From 430bfdf6ce4f56b1966852ac6dfd26eea626cccb Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 9 Aug 2023 08:21:36 +0800 Subject: [PATCH 030/104] Fix --- HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 45adaef8c7..76cf503928 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -51,6 +51,7 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.construct.JFXHyperlink; +import org.jackhuang.hmcl.util.CrashReporter; import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.ResourceNotFoundError; @@ -735,7 +736,7 @@ public static Image newRemoteImage(String url) { * algorithm or a faster one when scaling this image to fit within * the specified bounding box * @return the image resource within the jar. - * @see org.jackhuang.hmcl.util.CrashReporter + * @see CrashReporter * @see ResourceNotFoundError */ public static Image newRemoteImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth, boolean backgroundLoading) { @@ -760,7 +761,7 @@ public static Image newRemoteImage(String url, double requestedWidth, double req Image image = new Image(url, requestedWidth, requestedHeight, preserveRatio, smooth, backgroundLoading); image.progressProperty().addListener((observable, oldValue, newValue) -> { - if (newValue.doubleValue() == 1D && image.getWidth() != 0D && image.getHeight() != 0D) { + if (newValue.doubleValue() >= 1.0 && !image.isError() && image.getPixelReader() != null && image.getWidth() > 0.0 && image.getHeight() > 0.0) { Task.runAsync(() -> { Path path = Files.createTempFile("hmcl-net-resource-cache-", ".cache"); PNGJavaFXUtils.writeImage(image, path); From bbabfc3bfaa374c05861519a489a1333116c4e3a Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 9 Aug 2023 10:47:55 +0800 Subject: [PATCH 031/104] Fix --- .../hmcl/ui/construct/TwoLineListItem.java | 88 +++++++++++++++---- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 9a0f7cae44..0d3fb69b64 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -21,7 +21,6 @@ import javafx.animation.Interpolator; import javafx.animation.TranslateTransition; import javafx.application.Platform; -import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -41,14 +40,54 @@ import javafx.util.Duration; import org.jackhuang.hmcl.setting.ConfigHolder; import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.util.AggregatedObservableList; import org.jackhuang.hmcl.util.javafx.BindingMapping; -import org.jackhuang.hmcl.util.javafx.MappedObservableList; + +import java.util.List; +import java.util.function.Function; public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; private static final double TITLE_PART = 0.7D; + private static final class TagChangeListener implements ListChangeListener { + private final List list; + private final Function mapper; + private final Runnable callback; + + public TagChangeListener(List list, Function mapper, Runnable callback) { + this.list = list; + this.mapper = mapper; + this.callback = callback; + } + + @Override + public void onChanged(Change change) { + if (list != null) { + while (change.next()) { + if (change.wasPermutated()) { + for (int i = change.getFrom(); i < change.getTo(); i++) { + list.set(i, mapper.apply(change.getList().get(i))); + list.set(i + list.size() / 2, mapper.apply(change.getList().get(i))); + } + } else { + if (change.wasRemoved()) { + list.subList(change.getFrom() + list.size() / 2, change.getFrom() + change.getRemovedSize() + list.size() / 2).clear(); + list.subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear(); + } + if (change.wasAdded()) { + for (int i = 0; i < change.getAddedSubList().size(); i++) { + list.add(change.getFrom() + i + list.size() / 2, mapper.apply(change.getAddedSubList().get(i))); + list.add(change.getFrom() + i, mapper.apply(change.getAddedSubList().get(i))); + } + } + } + } + } + + Platform.runLater(callback); + } + } + private final StringProperty title = new SimpleStringProperty(this, "title"); private final ObservableList tags = FXCollections.observableArrayList(); private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle"); @@ -83,6 +122,7 @@ public TwoLineListItem() { titleLabel = new Label(); titleLabel.getStyleClass().add("title"); titleLabel.textProperty().bind(BindingMapping.of(title).map(s -> s + " | " + s)); + title.addListener(observable -> Platform.runLater(this::reLayout)); Rectangle titleClip = new Rectangle(0, 0, titleLabel.getWidth() / 2 - 10, titleLabel.getHeight()); titleClip.widthProperty().bind(BindingMapping.of(titleLabel.widthProperty()).map(w -> w.doubleValue() / 2 - 10)); @@ -93,17 +133,29 @@ public TwoLineListItem() { tagLabelsPane.getStyleClass().add("tag-pane"); tagBox = new HBox(); - AggregatedObservableList tagLabels = new AggregatedObservableList<>(); - for (int i = 0; i < 2; i++) { - tagLabels.appendList(MappedObservableList.create(tags, tag -> { - Label tagLabel = new Label(); - tagLabel.getStyleClass().add("tag"); - tagLabel.setText(tag); - HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); - return tagLabel; - })); - } - Bindings.bindContent(tagBox.getChildren(), tagLabels.getAggregatedList()); + tags.addListener(new TagChangeListener(tagBox.getChildren(), txt -> { + Label tagLabel = new Label(); + tagLabel.getStyleClass().add("tag"); + tagLabel.setText(txt); + HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); + return tagLabel; + }, this::reLayout)); + + // TODO Stable method, but it's quite slow. +// tags.addListener((ListChangeListener) observable -> { +// ArrayList list = new ArrayList<>(tags.size() * 2); +// for (int i = 0; i < 2; i++) { +// for (String tag : tags) { +// Label tagLabel = new Label(); +// tagLabel.getStyleClass().add("tag"); +// tagLabel.setText(tag); +// HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); +// list.add(tagLabel); +// } +// } +// tagBox.getChildren().setAll(list); +// Platform.runLater(this::reLayout); +// }); Rectangle tagClip = new Rectangle(0, 0, tagBox.getWidth() / 2 - 2, tagBox.getHeight()); tagClip.widthProperty().bind(BindingMapping.of(tagBox.widthProperty()).map(w -> w.doubleValue() / 2 - 2)); @@ -118,6 +170,7 @@ public TwoLineListItem() { subTitleLabel = new Label(); subTitleLabel.getStyleClass().add("subtitle"); subTitleLabel.textProperty().bind(BindingMapping.of(subtitle).map(s -> s + " | " + s)); + subtitle.addListener(observable -> Platform.runLater(this::reLayout)); Rectangle subTitleClip = new Rectangle(0, 0, subTitleLabel.getWidth() / 2 - 2, 80000); subTitleClip.widthProperty().bind(BindingMapping.of(subTitleLabel.widthProperty()).map(w -> w.doubleValue() / 2 - 2)); @@ -134,10 +187,6 @@ public TwoLineListItem() { }); getStyleClass().add(DEFAULT_STYLE_CLASS); - - title.addListener(observable -> Platform.runLater(this::reLayout)); - subtitle.addListener(observable -> Platform.runLater(this::reLayout)); - tags.addListener((ListChangeListener) c -> Platform.runLater(this::reLayout)); this.widthProperty().addListener(observable -> Platform.runLater(this::reLayout)); TranslateTransition titleTranslateTransition = new TranslateTransition(Duration.seconds(0.1D), titleLabel); @@ -175,7 +224,8 @@ public TwoLineListItem() { private static ChangeListener generateTranslateListener(TranslateTransition translateTransition) { if (ConfigHolder.config().isAnimationDisabled()) { - return (observable, oldValue, newValue) -> {}; + return (observable, oldValue, newValue) -> { + }; } else { return (observable, oldValue, newValue) -> { if (oldValue.booleanValue() != newValue.booleanValue()) { From 771dbea53e959ef099e24790229861577eb4a75f Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 9 Aug 2023 10:56:39 +0800 Subject: [PATCH 032/104] Fix --- .../java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 0d3fb69b64..3bd121cd12 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -249,7 +249,7 @@ private void reLayout() { double tagWidth = Math.min(tagBox.getWidth() / 2, tagMaxWidth); FXUtils.setLimitWidth((Pane) titleLabel.getParent(), titleWidth); - Rectangle titleClip = new Rectangle(0, 0, titleWidth - 6, titleLabel.getHeight()); + Rectangle titleClip = new Rectangle(0, 0, titleWidth - 7, titleLabel.getHeight()); titleLabel.getParent().setClip(titleClip); titleTranslatePlaying.set(titleLabel.getWidth() / 2 > titleMaxWidth); From 886fd037e43fc7effa1494cbcd7b2ef09b142f82 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 9 Aug 2023 11:46:40 +0800 Subject: [PATCH 033/104] Add license for RemoteResourceManager --- .../upgrade/resource/RemoteResourceManager.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java index 07d8aff2f3..bf985d3249 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.upgrade.resource; import com.google.gson.annotations.SerializedName; From 931c4b0bcb45584de2340f1bdb5a47b84b10cc37 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 9 Aug 2023 13:58:32 +0800 Subject: [PATCH 034/104] Remove TODO --- .../java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 3bd121cd12..1231a560b5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -141,7 +141,7 @@ public TwoLineListItem() { return tagLabel; }, this::reLayout)); - // TODO Stable method, but it's quite slow. + // Stable method, but it's quite slow. // tags.addListener((ListChangeListener) observable -> { // ArrayList list = new ArrayList<>(tags.size() * 2); // for (int i = 0; i < 2; i++) { From dfe96e4555a3594a07995a8a7946e14e81fdbbc8 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 9 Aug 2023 14:21:09 +0800 Subject: [PATCH 035/104] Enhance Chinese searching --- .../game/LocalizedRemoteModRepository.java | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java index 00704faec7..187d4716f9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java @@ -61,36 +61,56 @@ public SearchResult search(String gameVersion, Category category, int pageOffset for (int i = 0; i < searchFilter.length(); i++) { searchFilterLetters.add(searchFilter.subSequence(i, i + 1)); } - return new SearchResult(searchResult.getResults().map(remoteMod -> { - ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); - if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { - return Pair.pair(remoteMod, Integer.MAX_VALUE); - } - String chineseRemoteModName = chineseRemoteMod.getName(); - if (searchFilter.length() == 0 || chineseRemoteModName.length() == 0) { - return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length())); - } - int[][] lev = new int[searchFilter.length() + 1][chineseRemoteModName.length() + 1]; - for (int i = 0; i < chineseRemoteModName.length() + 1; i++) { - lev[0][i] = i; - } - for (int i = 0; i < searchFilter.length() + 1; i++) { - lev[i][0] = i; + + List chineseSearchResult = new ArrayList<>(); + List englishSearchResult = new ArrayList<>(); + searchResult.getResults().forEachOrdered(remoteMod -> { + ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); + if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) { + chineseSearchResult.add(remoteMod); + } else { + englishSearchResult.add(remoteMod); } - for (int i = 1; i < searchFilter.length() + 1; i++) { - for (int j = 1; j < chineseRemoteModName.length() + 1; j++) { - int countByInsert = lev[i][j - 1] + 1; - int countByDel = lev[i - 1][j] + 1; - int countByReplace = searchFilter.charAt(i - 1) == chineseRemoteModName.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; - lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); - } + }); + int totalPages = searchResult.getTotalPages(); + searchResult = null; // Release memory + + return new SearchResult(Stream.of(chineseSearchResult, englishSearchResult).flatMap(remoteMods -> { + if (remoteMods == chineseSearchResult) { + return chineseSearchResult.stream().map(remoteMod -> { + ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); + if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { + return Pair.pair(remoteMod, Integer.MAX_VALUE); + } + String chineseRemoteModName = chineseRemoteMod.getName(); + if (searchFilter.length() == 0 || chineseRemoteModName.length() == 0) { + return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length())); + } + int[][] lev = new int[searchFilter.length() + 1][chineseRemoteModName.length() + 1]; + for (int i = 0; i < chineseRemoteModName.length() + 1; i++) { + lev[0][i] = i; + } + for (int i = 0; i < searchFilter.length() + 1; i++) { + lev[i][0] = i; + } + for (int i = 1; i < searchFilter.length() + 1; i++) { + for (int j = 1; j < chineseRemoteModName.length() + 1; j++) { + int countByInsert = lev[i][j - 1] + 1; + int countByDel = lev[i - 1][j] + 1; + int countByReplace = searchFilter.charAt(i - 1) == chineseRemoteModName.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; + lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); + } + } + + return Pair.pair( + remoteMod, + lev[searchFilter.length()][chineseRemoteModName.length()] - (searchFilterLetters.stream().anyMatch(chineseRemoteModName::contains) ? CONTAIN_CHINESE_WEIGHT : 0) + ); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey); + } else { + return englishSearchResult.stream(); } - - return Pair.pair( - remoteMod, - lev[searchFilter.length()][chineseRemoteModName.length()] - (searchFilterLetters.stream().anyMatch(chineseRemoteModName::contains) ? CONTAIN_CHINESE_WEIGHT : 0) - ); - }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), searchResult.getTotalPages()); + }), totalPages); } @Override From 3effb3790dbd5bc6045b55fc7162bc19b3cc32ba Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Thu, 10 Aug 2023 13:35:05 +0800 Subject: [PATCH 036/104] Support to decode metadata for local quilt mod. --- .../java/org/jackhuang/hmcl/mod/Datapack.java | 1 + .../org/jackhuang/hmcl/mod/ModManager.java | 6 ++ .../mod/{ => modinfo}/FabricModMetadata.java | 5 +- .../{ => modinfo}/ForgeNewModMetadata.java | 5 +- .../{ => modinfo}/ForgeOldModMetadata.java | 5 +- .../mod/{ => modinfo}/LiteModMetadata.java | 5 +- .../hmcl/mod/{ => modinfo}/PackMcMeta.java | 5 +- .../hmcl/mod/modinfo/QuiltModMetadata.java | 81 +++++++++++++++++++ 8 files changed, 108 insertions(+), 5 deletions(-) rename HMCLCore/src/main/java/org/jackhuang/hmcl/mod/{ => modinfo}/FabricModMetadata.java (95%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/mod/{ => modinfo}/ForgeNewModMetadata.java (96%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/mod/{ => modinfo}/ForgeOldModMetadata.java (96%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/mod/{ => modinfo}/LiteModMetadata.java (95%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/mod/{ => modinfo}/PackMcMeta.java (97%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java index 7ec30301c1..4506179e51 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java @@ -23,6 +23,7 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import org.jackhuang.hmcl.mod.modinfo.PackMcMeta; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 538eeabf22..3b6cb56f44 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.mod; import org.jackhuang.hmcl.game.GameRepository; +import org.jackhuang.hmcl.mod.modinfo.*; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -89,6 +90,11 @@ public LocalModFile getModInfo(Path modFile) { } catch (Exception ignore) { } + try { + return QuiltModMetadata.fromFile(this, modFile, fs); + } catch (Exception ignore) { + } + try { return PackMcMeta.fromFile(this, modFile, fs); } catch (Exception ignore) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java similarity index 95% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java index caf915a308..9b0a721b32 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java @@ -15,10 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.mod; +package org.jackhuang.hmcl.mod.modinfo; import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModLoaderType; +import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java similarity index 96% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java index 84ede9dfd1..acc322671f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java @@ -1,7 +1,10 @@ -package org.jackhuang.hmcl.mod; +package org.jackhuang.hmcl.mod.modinfo; import com.google.gson.JsonParseException; import com.moandjiezana.toml.Toml; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModLoaderType; +import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.io.FileUtils; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java similarity index 96% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java index 9269b0d9b6..de4e9988b4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java @@ -15,11 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.mod; +package org.jackhuang.hmcl.mod.modinfo; import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModLoaderType; +import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java similarity index 95% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java index f80393a55b..d70eabaf56 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java @@ -15,9 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.mod; +package org.jackhuang.hmcl.mod.modinfo; import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModLoaderType; +import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.gson.JsonUtils; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java similarity index 97% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java index a973870efb..a4b027c960 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java @@ -15,11 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.mod; +package org.jackhuang.hmcl.mod.modinfo; import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModLoaderType; +import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.Validation; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java new file mode 100644 index 0000000000..a95a6ecbb7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java @@ -0,0 +1,81 @@ +package org.jackhuang.hmcl.mod.modinfo; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModLoaderType; +import org.jackhuang.hmcl.mod.ModManager; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.FileUtils; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Collectors; + +@Immutable +public final class QuiltModMetadata { + private static final class QuiltLoader { + private static final class Metadata { + private final String name; + private final String description; + private final JsonObject contributors; + private final String icon; + private final JsonObject contact; + + public Metadata(String name, String description, JsonObject contributors, String icon, JsonObject contact) { + this.name = name; + this.description = description; + this.contributors = contributors; + this.icon = icon; + this.contact = contact; + } + } + + private final String id; + private final String version; + private final Metadata metadata; + + public QuiltLoader(String id, String version, Metadata metadata) { + this.id = id; + this.version = version; + this.metadata = metadata; + } + } + + private final int schema_version; + private final QuiltLoader quilt_loader; + + public QuiltModMetadata(int schema_version, QuiltLoader quilt_loader) { + this.schema_version = schema_version; + this.quilt_loader = quilt_loader; + } + + public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + Path path = fs.getPath("quilt.mod.json"); + if (Files.notExists(path)) { + throw new IOException("File " + modFile + " is not a Quilt mod."); + } + + QuiltModMetadata root = JsonUtils.fromNonNullJson(FileUtils.readText(path), QuiltModMetadata.class); + if (root.schema_version != 1) { + throw new IOException("File " + modFile + " is not a supported Quilt mod."); + } + + return new LocalModFile( + modManager, + modManager.getLocalMod(root.quilt_loader.id, ModLoaderType.QUILT), + modFile, + root.quilt_loader.metadata.name, + new LocalModFile.Description(root.quilt_loader.metadata.description), + root.quilt_loader.metadata.contributors.entrySet().stream().map(entry -> String.format("%s (%s)", entry.getKey(), entry.getValue().getAsJsonPrimitive().getAsString())).collect(Collectors.joining(", ")), + root.quilt_loader.version, + "", + Optional.ofNullable(root.quilt_loader.metadata.contact.get("homepage")).map(jsonElement -> jsonElement.getAsJsonPrimitive().getAsString()).orElse(""), + root.quilt_loader.metadata.icon + ); + } +} From 1b94a9092db4a03ac826377cfe28935afb2ca91d Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Thu, 10 Aug 2023 14:25:14 +0800 Subject: [PATCH 037/104] Enhance ModManager --- .../org/jackhuang/hmcl/mod/ModManager.java | 81 ++++++++++--------- .../hmcl/mod/modinfo/FabricModMetadata.java | 4 +- .../hmcl/mod/modinfo/ForgeNewModMetadata.java | 5 +- .../hmcl/mod/modinfo/ForgeOldModMetadata.java | 5 +- .../hmcl/mod/modinfo/IModMetadataReader.java | 13 +++ .../hmcl/mod/modinfo/LiteModMetadata.java | 5 +- .../hmcl/mod/modinfo/PackMcMeta.java | 5 +- .../hmcl/mod/modinfo/QuiltModMetadata.java | 10 +-- 8 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 3b6cb56f44..0a1cd7e053 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -19,6 +19,8 @@ import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.mod.modinfo.*; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -26,12 +28,38 @@ import java.io.IOException; import java.nio.file.*; -import java.util.Collection; -import java.util.HashMap; -import java.util.Objects; -import java.util.TreeSet; +import java.util.*; +import java.util.stream.Collectors; public final class ModManager { + private static final class MetadataReaderStorage { + private final List readers; + + private final String defaultDesc; + + public MetadataReaderStorage(List readers, String defaultDesc) { + this.readers = readers; + this.defaultDesc = defaultDesc; + } + + public static MetadataReaderStorage of(List readers, String defaultDesc) { + return new MetadataReaderStorage(readers, defaultDesc); + } + } + + private final Map readers = Lang.mapOf(Lang.immutableListOf( + Pair.pair(Lang.immutableListOf("zip", "jar"), MetadataReaderStorage.of(Lang.immutableListOf( + new ForgeOldModMetadata(), + new ForgeNewModMetadata(), + new FabricModMetadata(), + new QuiltModMetadata(), + new PackMcMeta() + ), "")), + Pair.pair(Lang.immutableListOf("litemod"), MetadataReaderStorage.of(Lang.immutableListOf( + new LiteModMetadata() + ), "")) + ).stream().flatMap(pair -> pair.getKey().stream().map(extension -> Pair.pair(extension, pair.getValue()))).collect(Collectors.toList())); + private final GameRepository repository; private final String id; private final TreeSet localModFiles = new TreeSet<>(); @@ -72,51 +100,26 @@ private void addModInfo(Path file) { public LocalModFile getModInfo(Path modFile) { String fileName = StringUtils.removeSuffix(FileUtils.getName(modFile), DISABLED_EXTENSION, OLD_EXTENSION); - String description; - if (fileName.endsWith(".zip") || fileName.endsWith(".jar")) { - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile)) { - try { - return ForgeOldModMetadata.fromFile(this, modFile, fs); - } catch (Exception ignore) { - } - - try { - return ForgeNewModMetadata.fromFile(this, modFile, fs); - } catch (Exception ignore) { - } - - try { - return FabricModMetadata.fromFile(this, modFile, fs); - } catch (Exception ignore) { - } - - try { - return QuiltModMetadata.fromFile(this, modFile, fs); - } catch (Exception ignore) { - } + String extension = fileName.substring(fileName.lastIndexOf(".") + 1); + if (!readers.containsKey(extension)) { + throw new IllegalArgumentException("File " + modFile + " is not a mod file."); + } + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile)) { + for (IModMetadataReader reader : readers.get(extension).readers) { try { - return PackMcMeta.fromFile(this, modFile, fs); + return reader.fromFile(this, modFile, fs); } catch (Exception ignore) { } - } catch (Exception ignored) { - } - - description = ""; - } else if (fileName.endsWith(".litemod")) { - try { - return LiteModMetadata.fromFile(this, modFile); - } catch (Exception ignore) { - description = "LiteLoader Mod"; } - } else { - throw new IllegalArgumentException("File " + modFile + " is not a mod file."); + } catch (Exception ignored) { } + return new LocalModFile(this, getLocalMod(FileUtils.getNameWithoutExtension(modFile), ModLoaderType.UNKNOWN), modFile, FileUtils.getNameWithoutExtension(modFile), - new LocalModFile.Description(description)); + new LocalModFile.Description(readers.get(extension).defaultDesc)); } public void refreshMods() throws IOException { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java index 9b0a721b32..95049b9d73 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java @@ -37,7 +37,7 @@ import java.util.stream.Collectors; @Immutable -public final class FabricModMetadata { +public final class FabricModMetadata implements IModMetadataReader { private final String id; private final String name; private final String version; @@ -60,7 +60,7 @@ public FabricModMetadata(String id, String name, String version, String icon, St this.contact = contact; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path mcmod = fs.getPath("fabric.mod.json"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a Fabric mod."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java index acc322671f..28fa252586 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java @@ -22,8 +22,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG; @Immutable -public final class ForgeNewModMetadata { - +public final class ForgeNewModMetadata implements IModMetadataReader { private final String modLoader; private final String loaderVersion; @@ -118,7 +117,7 @@ public String getDescription() { } } - public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path modstoml = fs.getPath("META-INF/mods.toml"); if (Files.notExists(modstoml)) throw new IOException("File " + modFile + " is not a Forge 1.13+ mod."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java index de4e9988b4..9472057526 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java @@ -39,8 +39,7 @@ * @author huangyuhui */ @Immutable -public final class ForgeOldModMetadata { - +public final class ForgeOldModMetadata implements IModMetadataReader { @SerializedName("modid") private final String modId; private final String name; @@ -122,7 +121,7 @@ public String[] getAuthors() { return authors; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path mcmod = fs.getPath("mcmod.info"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a Forge mod."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java new file mode 100644 index 0000000000..4397ae7a00 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java @@ -0,0 +1,13 @@ +package org.jackhuang.hmcl.mod.modinfo; + +import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModManager; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Path; + +public interface IModMetadataReader { + LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java index d70eabaf56..560bb97b8b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java @@ -25,6 +25,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils; import java.io.IOException; +import java.nio.file.FileSystem; import java.nio.file.Path; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -34,7 +35,7 @@ * @author huangyuhui */ @Immutable -public final class LiteModMetadata { +public final class LiteModMetadata implements IModMetadataReader { private final String name; private final String version; private final String mcversion; @@ -109,7 +110,7 @@ public String getUpdateURI() { return updateURI; } - public static LocalModFile fromFile(ModManager modManager, Path modFile) throws IOException, JsonParseException { + public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { try (ZipFile zipFile = new ZipFile(modFile.toFile())) { ZipEntry entry = zipFile.getEntry("litemod.json"); if (entry == null) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java index a4b027c960..10bc0c900a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java @@ -38,8 +38,7 @@ import java.util.List; @Immutable -public class PackMcMeta implements Validation { - +public class PackMcMeta implements Validation, IModMetadataReader { @SerializedName("pack") private final PackInfo pack; @@ -145,7 +144,7 @@ public PackInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationC } } - public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path mcmod = fs.getPath("pack.mcmeta"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a resource pack."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java index a95a6ecbb7..a68f803cb4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java @@ -17,7 +17,7 @@ import java.util.stream.Collectors; @Immutable -public final class QuiltModMetadata { +public final class QuiltModMetadata implements IModMetadataReader { private static final class QuiltLoader { private static final class Metadata { private final String name; @@ -49,12 +49,12 @@ public QuiltLoader(String id, String version, Metadata metadata) { private final int schema_version; private final QuiltLoader quilt_loader; - public QuiltModMetadata(int schema_version, QuiltLoader quilt_loader) { - this.schema_version = schema_version; - this.quilt_loader = quilt_loader; + public QuiltModMetadata() { + this.schema_version = -1; + this.quilt_loader = null; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path path = fs.getPath("quilt.mod.json"); if (Files.notExists(path)) { throw new IOException("File " + modFile + " is not a Quilt mod."); From d5996123454d54dbbd97f931d0f43b03f1e307cc Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Thu, 10 Aug 2023 14:28:27 +0800 Subject: [PATCH 038/104] Fix checkstyle --- HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 0a1cd7e053..fc934c08e5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -48,7 +48,7 @@ public static MetadataReaderStorage of(List readers, String } private final Map readers = Lang.mapOf(Lang.immutableListOf( - Pair.pair(Lang.immutableListOf("zip", "jar"), MetadataReaderStorage.of(Lang.immutableListOf( + Pair.pair(Lang.immutableListOf("zip", "jar"), MetadataReaderStorage.of(Lang.immutableListOf( new ForgeOldModMetadata(), new ForgeNewModMetadata(), new FabricModMetadata(), From 82a6a0c09f76696c455a73c422c1e1fc9f271efb Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Thu, 10 Aug 2023 15:16:19 +0800 Subject: [PATCH 039/104] Refactor --- .../org/jackhuang/hmcl/mod/ModManager.java | 35 ++++--------- .../hmcl/mod/modinfo/IModMetadataReader.java | 50 +++++++++++++++++++ 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index fc934c08e5..3abe43a46b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -19,8 +19,6 @@ import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.mod.modinfo.*; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -29,36 +27,20 @@ import java.io.IOException; import java.nio.file.*; import java.util.*; -import java.util.stream.Collectors; public final class ModManager { - private static final class MetadataReaderStorage { - private final List readers; - - private final String defaultDesc; - - public MetadataReaderStorage(List readers, String defaultDesc) { - this.readers = readers; - this.defaultDesc = defaultDesc; - } - - public static MetadataReaderStorage of(List readers, String defaultDesc) { - return new MetadataReaderStorage(readers, defaultDesc); - } - } - - private final Map readers = Lang.mapOf(Lang.immutableListOf( - Pair.pair(Lang.immutableListOf("zip", "jar"), MetadataReaderStorage.of(Lang.immutableListOf( + private final Map readers = IModMetadataReader.ofStorage( + IModMetadataReader.MetadataReaderStorage.ofExtensions("zip", "jar").ofReaders( new ForgeOldModMetadata(), new ForgeNewModMetadata(), new FabricModMetadata(), new QuiltModMetadata(), new PackMcMeta() - ), "")), - Pair.pair(Lang.immutableListOf("litemod"), MetadataReaderStorage.of(Lang.immutableListOf( + ).ofDesc(""), + IModMetadataReader.MetadataReaderStorage.ofExtensions("litemod").ofReaders( new LiteModMetadata() - ), "")) - ).stream().flatMap(pair -> pair.getKey().stream().map(extension -> Pair.pair(extension, pair.getValue()))).collect(Collectors.toList())); + ).ofDesc("LiteLoader Mod") + ); private final GameRepository repository; private final String id; @@ -106,7 +88,7 @@ public LocalModFile getModInfo(Path modFile) { } try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile)) { - for (IModMetadataReader reader : readers.get(extension).readers) { + for (IModMetadataReader reader : readers.get(extension).getReaders()) { try { return reader.fromFile(this, modFile, fs); } catch (Exception ignore) { @@ -119,7 +101,8 @@ public LocalModFile getModInfo(Path modFile) { getLocalMod(FileUtils.getNameWithoutExtension(modFile), ModLoaderType.UNKNOWN), modFile, FileUtils.getNameWithoutExtension(modFile), - new LocalModFile.Description(readers.get(extension).defaultDesc)); + new LocalModFile.Description(readers.get(extension).getDefaultDesc()) + ); } public void refreshMods() throws IOException { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java index 4397ae7a00..67805f28e4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java @@ -7,7 +7,57 @@ import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; public interface IModMetadataReader { LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException; + + static Map ofStorage(MetadataReaderStorage... storages) { + Map storageMap = new HashMap<>(); + for (MetadataReaderStorage storage : storages) { + if (storage.extensions == null || storage.defaultDesc == null) { + throw new IllegalArgumentException(); + } + for (String extension : storage.extensions) { + storageMap.put(extension, storage); + } + storage.extensions = null; + } + return storageMap; + } + + final class MetadataReaderStorage { + private String[] extensions; + + private IModMetadataReader[] readers = new IModMetadataReader[0]; + + private String defaultDesc = null; + + private MetadataReaderStorage(String... extensions) { + this.extensions = extensions; + } + + public static MetadataReaderStorage ofExtensions(String... extensions) { + return new MetadataReaderStorage(extensions); + } + + public MetadataReaderStorage ofReaders(IModMetadataReader... readers) { + this.readers = readers; + return this; + } + + public MetadataReaderStorage ofDesc(String defaultDesc) { + this.defaultDesc = defaultDesc; + return this; + } + + public IModMetadataReader[] getReaders() { + return this.readers; + } + + public String getDefaultDesc() { + return this.defaultDesc; + } + } } From 7e45fe85716426f5f804a78cad186bb93a4554cf Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 11 Aug 2023 12:15:40 +0800 Subject: [PATCH 040/104] Fix --- .../org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 1231a560b5..62f3c9fe4c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -44,6 +44,9 @@ import java.util.List; import java.util.function.Function; +import java.util.logging.Level; + +import static org.jackhuang.hmcl.util.Logging.LOG; public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; @@ -241,7 +244,11 @@ private static ChangeListener generateTranslateListener(TranslateTransi } private void reLayout() { - this.layout(); + try { + this.layout(); + } catch (NullPointerException npe) { + LOG.log(Level.WARNING, "For some unknown reasons, invoking this::layout too fast may produce NullPointerException.", npe); + } double titleMaxWidth = tags.size() == 0 ? this.getWidth() : this.getWidth() * TITLE_PART; double titleWidth = Math.min(titleLabel.getWidth() / 2, titleMaxWidth); From 5543cd1e6a96388b7aedad52c09097c7082f7b6e Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 11 Aug 2023 19:26:42 +0800 Subject: [PATCH 041/104] Fix --- .../hmcl/ui/versions/DownloadListPage.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 41d58e0b2a..17ad04b11c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -346,14 +346,21 @@ protected ModDownloadListPageSkin(DownloadListPage control) { sortComboBox.getSelectionModel().select(0); searchPane.addRow(rowIndex++, new Label(i18n("mods.category")), categoryStackPane, new Label(i18n("search.sort")), sortStackPane); - EventHandler searchAction = e -> getSkinnable() - .search(gameVersionField.getSelectionModel().getSelectedItem(), - Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem()) - .map(CategoryIndented::getCategory) - .orElse(null), - control.pageOffset.get(), - nameField.getText(), - sortComboBox.getSelectionModel().getSelectedItem()); + StringProperty previousSearchFilter = new SimpleStringProperty(this, "Previous Seach Filter", ""); + EventHandler searchAction = e -> { + if (!previousSearchFilter.get().equals(nameField.getText())) { + control.pageOffset.set(0); + } + + previousSearchFilter.set(nameField.getText()); + getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(), + Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem()) + .map(CategoryIndented::getCategory) + .orElse(null), + control.pageOffset.get(), + nameField.getText(), + sortComboBox.getSelectionModel().getSelectedItem()); + }; HBox actionsBox = new HBox(8); GridPane.setColumnSpan(actionsBox, 4); @@ -396,10 +403,10 @@ protected ModDownloadListPageSkin(DownloadListPage control) { }); nextPageButton.setDisable(true); control.pageOffset.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable( - control.pageCount.get() == -1 || control.pageOffset.get() == control.pageCount.get() - 1 + control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1 )); control.pageCount.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable( - control.pageCount.get() == -1 || control.pageOffset.get() == control.pageCount.get() - 1 + control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1 )); JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page")); From 953558da77af5a0fe3153e77cdcb9b6affa30ffa Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 11 Aug 2023 21:53:47 +0800 Subject: [PATCH 042/104] Refactor DownloadPage --- .../hmcl/ui/versions/DownloadPage.java | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 6e09c00d90..c36df99f7e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -320,10 +320,50 @@ protected ModDownloadPageSkin(DownloadPage control) { for (String gameVersion : control.versions.keys().stream() .sorted(VersionNumber.VERSION_COMPARATOR.reversed()) .collect(Collectors.toList())) { - ComponentList sublist = new ComponentList(() -> - control.versions.get(gameVersion).stream() - .map(version -> new ModItem(version, control)) - .collect(Collectors.toList())); + ComponentList sublist = new ComponentList(() -> { + Collection versions = control.versions.get(gameVersion); + List res = new ArrayList<>(versions.size() + 1); + + VBox dependenciesBox = new VBox(8); + versions.stream().findFirst().ifPresent(dataItem -> { + if (control.repository.getType() == RemoteModRepository.Type.MOD) { + EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); + try { + for (RemoteMod.Dependency dependency : dataItem.getDependencies()) { + if (dependency.getType() == INCOMPATIBLE || dependency.getType() == BROKEN) { + continue; + } + + if (!dependencies.containsKey(dependency.getType())) { + dependencies.put(dependency.getType(), new ArrayList<>()); + } + dependencies.get(dependency.getType()).add(new DependencyModItem(control.page, dependency.load(), control.version, control.callback)); + } + } catch (IOException exception) { + dependencies.clear(); + Label msg = new Label(i18n("download.failed.refresh")); + msg.setPadding(new Insets(8)); + dependenciesBox.getChildren().setAll(msg); + LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid()), exception); + } + + for (Map.Entry> entry : dependencies.entrySet()) { + Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(entry.getKey()))); + title.setPadding(new Insets(0, 8, 0, 8)); + dependenciesBox.getChildren().setAll(title); + dependenciesBox.getChildren().addAll(entry.getValue()); + } + } + }); + dependenciesBox.setPadding(new Insets(0, 0, 8, 0)); + res.add(dependenciesBox); + + for (RemoteMod.Version version : versions) { + res.add(new ModItem(version, control)); + } + + return res; + }); sublist.getStyleClass().add("no-padding"); sublist.setTitle(gameVersion); @@ -380,37 +420,6 @@ private static final class ModItem extends StackPane { pane.setPadding(new Insets(8, 0, 8, 0)); { - if (selfPage.repository.getType() == RemoteModRepository.Type.MOD) { - List elements = new ArrayList<>(); - EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); - try { - for (RemoteMod.Dependency dependency : dataItem.getDependencies()) { - if (dependency.getType() == INCOMPATIBLE || dependency.getType() == BROKEN) { - continue; - } - - if (!dependencies.containsKey(dependency.getType())) { - dependencies.put(dependency.getType(), new ArrayList<>()); - } - dependencies.get(dependency.getType()).add(new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback)); - } - } catch (IOException exception) { - dependencies.clear(); - Label msg = new Label(i18n("download.failed.refresh")); - msg.setPadding(new Insets(8)); - elements.add(msg); - LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid()), exception); - } - - for (Map.Entry> entry : dependencies.entrySet()) { - Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(entry.getKey()))); - title.setPadding(new Insets(0, 8, 0, 8)); - elements.add(title); - elements.addAll(entry.getValue()); - } - pane.getChildren().addAll(elements); - } - HBox descPane = new HBox(8); descPane.setPadding(new Insets(0, 8, 0, 8)); descPane.setAlignment(Pos.CENTER_LEFT); From 17c076bafea8a31fe9e3f7ed46fb7b12f28b31ba Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 11 Aug 2023 21:55:15 +0800 Subject: [PATCH 043/104] Fix --- .../java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 14e7ddab5d..7f7c4832d5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -334,8 +334,8 @@ class ModInfoDialog extends JFXDialogLayout { case FORGE: case LITE_LOADER: case QUILT: { - if (!title.getTags().contains(modLoaderType.name())) { - title.getTags().add(modLoaderType.name()); + if (!title.getTags().contains(modLoaderType.getLoaderName())) { + title.getTags().add(modLoaderType.getLoaderName()); } } } From 45dd87fb75d09fd7385cb5b5ad32cce73cb492ae Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 12 Aug 2023 10:18:21 +0800 Subject: [PATCH 044/104] Revert "Refactor DownloadPage" This reverts commit 953558da77af5a0fe3153e77cdcb9b6affa30ffa. --- .../hmcl/ui/versions/DownloadPage.java | 79 ++++++++----------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index c36df99f7e..6e09c00d90 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -320,50 +320,10 @@ protected ModDownloadPageSkin(DownloadPage control) { for (String gameVersion : control.versions.keys().stream() .sorted(VersionNumber.VERSION_COMPARATOR.reversed()) .collect(Collectors.toList())) { - ComponentList sublist = new ComponentList(() -> { - Collection versions = control.versions.get(gameVersion); - List res = new ArrayList<>(versions.size() + 1); - - VBox dependenciesBox = new VBox(8); - versions.stream().findFirst().ifPresent(dataItem -> { - if (control.repository.getType() == RemoteModRepository.Type.MOD) { - EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); - try { - for (RemoteMod.Dependency dependency : dataItem.getDependencies()) { - if (dependency.getType() == INCOMPATIBLE || dependency.getType() == BROKEN) { - continue; - } - - if (!dependencies.containsKey(dependency.getType())) { - dependencies.put(dependency.getType(), new ArrayList<>()); - } - dependencies.get(dependency.getType()).add(new DependencyModItem(control.page, dependency.load(), control.version, control.callback)); - } - } catch (IOException exception) { - dependencies.clear(); - Label msg = new Label(i18n("download.failed.refresh")); - msg.setPadding(new Insets(8)); - dependenciesBox.getChildren().setAll(msg); - LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid()), exception); - } - - for (Map.Entry> entry : dependencies.entrySet()) { - Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(entry.getKey()))); - title.setPadding(new Insets(0, 8, 0, 8)); - dependenciesBox.getChildren().setAll(title); - dependenciesBox.getChildren().addAll(entry.getValue()); - } - } - }); - dependenciesBox.setPadding(new Insets(0, 0, 8, 0)); - res.add(dependenciesBox); - - for (RemoteMod.Version version : versions) { - res.add(new ModItem(version, control)); - } - - return res; - }); + ComponentList sublist = new ComponentList(() -> + control.versions.get(gameVersion).stream() + .map(version -> new ModItem(version, control)) + .collect(Collectors.toList())); sublist.getStyleClass().add("no-padding"); sublist.setTitle(gameVersion); @@ -420,6 +380,37 @@ private static final class ModItem extends StackPane { pane.setPadding(new Insets(8, 0, 8, 0)); { + if (selfPage.repository.getType() == RemoteModRepository.Type.MOD) { + List elements = new ArrayList<>(); + EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); + try { + for (RemoteMod.Dependency dependency : dataItem.getDependencies()) { + if (dependency.getType() == INCOMPATIBLE || dependency.getType() == BROKEN) { + continue; + } + + if (!dependencies.containsKey(dependency.getType())) { + dependencies.put(dependency.getType(), new ArrayList<>()); + } + dependencies.get(dependency.getType()).add(new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback)); + } + } catch (IOException exception) { + dependencies.clear(); + Label msg = new Label(i18n("download.failed.refresh")); + msg.setPadding(new Insets(8)); + elements.add(msg); + LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid()), exception); + } + + for (Map.Entry> entry : dependencies.entrySet()) { + Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(entry.getKey()))); + title.setPadding(new Insets(0, 8, 0, 8)); + elements.add(title); + elements.addAll(entry.getValue()); + } + pane.getChildren().addAll(elements); + } + HBox descPane = new HBox(8); descPane.setPadding(new Insets(0, 8, 0, 8)); descPane.setAlignment(Pos.CENTER_LEFT); From 8345dd5584faed4b7a1768098d43c97ee70cd12a Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 12 Aug 2023 15:08:24 +0800 Subject: [PATCH 045/104] Refactor DownloadPage --- .../hmcl/ui/construct/ComponentList.java | 26 +--- .../hmcl/ui/construct/ComponentListCell.java | 22 ++-- .../hmcl/ui/versions/DownloadPage.java | 114 +++++++++++------- 3 files changed, 82 insertions(+), 80 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java index 682fb24e9a..03dd49b875 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.ui.construct; -import javafx.application.Platform; import javafx.beans.DefaultProperty; import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; @@ -37,8 +36,6 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.javafx.MappedObservableList; import java.util.List; @@ -52,7 +49,6 @@ public class ComponentList extends Control { private boolean hasSubtitle = false; public final ObservableList content = FXCollections.observableArrayList(); private Supplier> lazyInitializer; - private SpinnerPane spinner; public ComponentList() { getStyleClass().add("options-list"); @@ -61,10 +57,6 @@ public ComponentList() { public ComponentList(Supplier> lazyInitializer) { this(); this.lazyInitializer = lazyInitializer; - spinner = new SpinnerPane(); - spinner.getStyleClass().add("small-spinner-pane"); - spinner.showSpinner(); - this.getContent().setAll(spinner); } public String getTitle() { @@ -115,24 +107,12 @@ public ObservableList getContent() { return content; } - void doLazyInit(Runnable callback) { + void doLazyInit() { if (lazyInitializer != null) { + this.getContent().setAll(lazyInitializer.get()); setNeedsLayout(true); - - Task.supplyAsync(lazyInitializer::get) - .whenComplete(Schedulers.javafx(), (result, exception) -> { - if (exception == null) { - spinner.hideSpinner(); - this.getContent().setAll(result); - setNeedsLayout(true); - lazyInitializer = null; - } else { - spinner.setLoading(false); - } - Platform.runLater(callback); - }).start(); + lazyInitializer = null; } - callback.run(); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java index 984e89a092..ed9dc7314d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -32,10 +32,7 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.scene.shape.Rectangle; import javafx.util.Duration; import org.jackhuang.hmcl.setting.Theme; @@ -149,8 +146,12 @@ private void updateLayout() { boolean expanded = !isExpanded(); setExpanded(expanded); + if (expanded) { + list.doLazyInit(); + list.layout(); + } - Runnable callback = () -> { + Platform.runLater(() -> { double newAnimatedHeight = (list.prefHeight(-1) + 8 + 10) * (expanded ? 1 : -1); double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(-1); double contentHeight = expanded ? newAnimatedHeight : 0; @@ -178,16 +179,7 @@ private void updateLayout() { updateClip(newHeight); } } - }; - - if (expanded) { - list.doLazyInit(() -> { - list.layout(); - callback.run(); - }); - } else { - Platform.runLater(callback); - } + }); }; headerRippler.setOnMouseClicked(onExpand); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 6e09c00d90..8cc93e2ae5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialogLayout; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyObjectProperty; @@ -47,11 +48,11 @@ import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.NetworkUtils; +import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.io.IOException; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; @@ -61,9 +62,9 @@ import java.util.stream.Stream; import static org.jackhuang.hmcl.mod.RemoteMod.DependencyType.*; +import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; public class DownloadPage extends Control implements DecoratorPage { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); @@ -271,8 +272,8 @@ protected ModDownloadPageSkin(DownloadPage control) { } if (control.repository.getType() != RemoteModRepository.Type.MOD) { - Node title = ComponentList.createComponentListTitle(i18n("mods.dependency.embedded")); ComponentList dependencyPane = new ComponentList(Lang::immutableListOf); + dependencyPane.setTitle(i18n("mods.dependency.embedded")); Task.supplyAsync(() -> control.addon.getData().loadDependencies(control.repository).stream() .map(remoteMod -> new DependencyModItem(getSkinnable().page, remoteMod, control.version, control.callback)) .map(dependencyModItem -> { @@ -293,7 +294,7 @@ protected ModDownloadPageSkin(DownloadPage control) { }).start(); dependencyPane.getStyleClass().add("no-padding"); - pane.getChildren().addAll(title, dependencyPane); + pane.getChildren().addAll(dependencyPane); } SpinnerPane spinnerPane = new SpinnerPane(); @@ -380,37 +381,6 @@ private static final class ModItem extends StackPane { pane.setPadding(new Insets(8, 0, 8, 0)); { - if (selfPage.repository.getType() == RemoteModRepository.Type.MOD) { - List elements = new ArrayList<>(); - EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); - try { - for (RemoteMod.Dependency dependency : dataItem.getDependencies()) { - if (dependency.getType() == INCOMPATIBLE || dependency.getType() == BROKEN) { - continue; - } - - if (!dependencies.containsKey(dependency.getType())) { - dependencies.put(dependency.getType(), new ArrayList<>()); - } - dependencies.get(dependency.getType()).add(new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback)); - } - } catch (IOException exception) { - dependencies.clear(); - Label msg = new Label(i18n("download.failed.refresh")); - msg.setPadding(new Insets(8)); - elements.add(msg); - LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", dataItem.getModid()), exception); - } - - for (Map.Entry> entry : dependencies.entrySet()) { - Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(entry.getKey()))); - title.setPadding(new Insets(0, 8, 0, 8)); - elements.add(title); - elements.addAll(entry.getValue()); - } - pane.getChildren().addAll(elements); - } - HBox descPane = new HBox(8); descPane.setPadding(new Insets(0, 8, 0, 8)); descPane.setAlignment(Pos.CENTER_LEFT); @@ -451,13 +421,8 @@ private static final class ModItem extends StackPane { } } - JFXButton saveAsButton = new JFXButton(); - saveAsButton.getStyleClass().add("toggle-icon4"); - saveAsButton.setGraphic(SVG.contentSaveMoveOutline(Theme.blackFillBinding(), -1, -1)); - saveAsButton.setOnAction(e -> selfPage.saveAs(dataItem)); - - descPane.getChildren().setAll(graphicPane, content, saveAsButton); - descPane.setOnMouseClicked(e -> selfPage.download(dataItem)); + descPane.getChildren().setAll(graphicPane, content); + descPane.setOnMouseClicked(e -> Controllers.dialog(new ModVersion(dataItem, selfPage))); } pane.getChildren().add(descPane); @@ -471,6 +436,71 @@ private static final class ModItem extends StackPane { } } + private static final class ModVersion extends JFXDialogLayout { + public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { + this.setHeading(new HBox(new Label(i18n("mods.download.title", version.getName())))); + + VBox box = new VBox(8); + box.setPadding(new Insets(8)); + box.getChildren().setAll(new ModItem(version, selfPage)); + if (selfPage.repository.getType() == RemoteModRepository.Type.MOD) { + ScrollPane scrollPane = new ScrollPane(); + ComponentList dependenciesList = new ComponentList(Lang::immutableListOf); + Task.supplyAsync(() -> { + EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); + for (RemoteMod.Dependency dependency : version.getDependencies()) { + if (dependency.getType() == INCOMPATIBLE || dependency.getType() == BROKEN) { + continue; + } + + if (!dependencies.containsKey(dependency.getType())) { + List list = new ArrayList<>(); + Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(dependency.getType()))); + title.setPadding(new Insets(0, 8, 0, 8)); + list.add(title); + dependencies.put(dependency.getType(), list); + } + dependencies.get(dependency.getType()).add(new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback)); + } + + return dependencies.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + dependenciesList.getContent().setAll(result); + } else { + Label msg = new Label(i18n("download.failed.refresh")); + msg.setPadding(new Insets(8)); + LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", version.getModid()), exception); + dependenciesList.getContent().setAll(msg); + } + }).start(); + + scrollPane.setContent(dependenciesList); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + box.getChildren().add(scrollPane); + + 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)); + } + this.setBody(box); + + JFXButton downloadButton = new JFXButton(i18n("download")); + downloadButton.getStyleClass().add("dialog-accept"); + downloadButton.setOnAction(e -> selfPage.download(version)); + + JFXButton saveAsButton = new JFXButton(i18n("button.save_as")); + saveAsButton.getStyleClass().add("dialog-accept"); + saveAsButton.setOnAction(e -> selfPage.saveAs(version)); + + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.getStyleClass().add("dialog-cancel"); + cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); + + this.setActions(downloadButton, saveAsButton, cancelButton); + } + } + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault()); public interface DownloadCallback { From f3b21d6968270e9f585bf8a5844af89a82d22af4 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 12 Aug 2023 16:19:01 +0800 Subject: [PATCH 046/104] Refactor --- .../hmcl/ui/versions/DownloadPage.java | 46 ++++++++++++++++++- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 8cc93e2ae5..2b9b6c525d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -31,6 +31,7 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.stage.FileChooser; +import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.mod.RemoteMod; @@ -318,6 +319,38 @@ protected ModDownloadPageSkin(DownloadPage control) { FXUtils.onChangeAndOperate(control.loaded, loaded -> { if (control.versions == null) return; + if (control.version != null) { + String currentGameVersion = null; + for (Version patches : control.version.getProfile().getRepository().getVersion(control.version.getVersion()).getPatches()) { + if (patches.getId().equals("game")) { + currentGameVersion = patches.getVersion(); + break; + } + } + + if (currentGameVersion != null && control.versions.containsKey(currentGameVersion) && control.versions.get(currentGameVersion).stream().findFirst().isPresent()) { + list.getContent().addAll( + ComponentList.createComponentListTitle(i18n("mods.download.recommend", currentGameVersion)), + new ModItem(control.versions.get(currentGameVersion).stream().findFirst().get(), control) + ); + + for (String gameVersion : control.versions.keys().stream() + .sorted(VersionNumber.VERSION_COMPARATOR.reversed()) + .collect(Collectors.toList())) { + ComponentList sublist = new ComponentList(() -> + control.versions.get(gameVersion).stream() + .map(version -> new ModItem(version, control)) + .collect(Collectors.toList())); + sublist.getStyleClass().add("no-padding"); + sublist.setTitle(gameVersion); + + list.getContent().add(sublist); + } + + return; + } + } + for (String gameVersion : control.versions.keys().stream() .sorted(VersionNumber.VERSION_COMPARATOR.reversed()) .collect(Collectors.toList())) { @@ -444,6 +477,7 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { box.setPadding(new Insets(8)); box.getChildren().setAll(new ModItem(version, selfPage)); if (selfPage.repository.getType() == RemoteModRepository.Type.MOD) { + SpinnerPane spinnerPane = new SpinnerPane(); ScrollPane scrollPane = new ScrollPane(); ComponentList dependenciesList = new ComponentList(Lang::immutableListOf); Task.supplyAsync(() -> { @@ -460,25 +494,33 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { list.add(title); dependencies.put(dependency.getType(), list); } - dependencies.get(dependency.getType()).add(new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback)); + DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback); + dependencyModItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + dependencies.get(dependency.getType()).add(dependencyModItem); } return dependencies.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); }).whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception == null) { + spinnerPane.setLoading(false); dependenciesList.getContent().setAll(result); } else { + spinnerPane.setLoading(false); + spinnerPane.setFailedReason(i18n("download.failed.refresh")); Label msg = new Label(i18n("download.failed.refresh")); msg.setPadding(new Insets(8)); LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", version.getModid()), exception); dependenciesList.getContent().setAll(msg); } }).start(); + spinnerPane.setLoading(true); scrollPane.setContent(dependenciesList); scrollPane.setFitToWidth(true); scrollPane.setFitToHeight(true); - box.getChildren().add(scrollPane); + spinnerPane.setContent(scrollPane); + box.getChildren().add(spinnerPane); + VBox.setVgrow(spinnerPane, Priority.SOMETIMES); 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)); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index d83647f451..7d2e0dcbc8 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -882,6 +882,7 @@ mods.dependency.broken=Broken pre-mod (This premod used to exist on the mod repo mods.disable=Disable mods.download=Mod Download mods.download.title=Mod Download - %1s +mods.download.recommend=Recommended Mod Version - %1s mods.enable=Enable mods.manage=Manage Mods mods.mcbbs=MCBBS diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index a79c670f91..29639da96d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -752,6 +752,7 @@ mods.dependency.broken=損壞的前綴(這個前綴曾經存在於 mod 倉庫 mods.disable=停用 mods.download=模組下載 mods.download.title=模組下載 - %1s +mods.download.recommend=推薦版本 - %1s mods.enable=啟用 mods.manage=模組管理 mods.mcbbs=MCBBS diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 1d2818b190..c5b097bb70 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -751,6 +751,7 @@ mods.dependency.broken=损坏的前置模组(该前置模组曾经在该模组 mods.disable=禁用 mods.download=模组下载 mods.download.title=模组下载 - %1s +mods.download.recommend=推荐版本 - %1s mods.enable=启用 mods.manage=模组管理 mods.mcbbs=MCBBS From a86bfcf274509a195cd21054c5fb0b2ae3af34c6 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 12 Aug 2023 17:31:48 +0800 Subject: [PATCH 047/104] Fix --- .../hmcl/ui/construct/TwoLineListItem.java | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 62f3c9fe4c..5ed72d0047 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -21,6 +21,7 @@ import javafx.animation.Interpolator; import javafx.animation.TranslateTransition; import javafx.application.Platform; +import javafx.beans.InvalidationListener; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -40,6 +41,7 @@ import javafx.util.Duration; import org.jackhuang.hmcl.setting.ConfigHolder; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.javafx.BindingMapping; import java.util.List; @@ -52,15 +54,13 @@ public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; private static final double TITLE_PART = 0.7D; - private static final class TagChangeListener implements ListChangeListener { + private final class TagChangeListener implements ListChangeListener { private final List list; private final Function mapper; - private final Runnable callback; - public TagChangeListener(List list, Function mapper, Runnable callback) { + public TagChangeListener(List list, Function mapper) { this.list = list; this.mapper = mapper; - this.callback = callback; } @Override @@ -86,8 +86,6 @@ public void onChanged(Change change) { } } } - - Platform.runLater(callback); } } @@ -125,7 +123,6 @@ public TwoLineListItem() { titleLabel = new Label(); titleLabel.getStyleClass().add("title"); titleLabel.textProperty().bind(BindingMapping.of(title).map(s -> s + " | " + s)); - title.addListener(observable -> Platform.runLater(this::reLayout)); Rectangle titleClip = new Rectangle(0, 0, titleLabel.getWidth() / 2 - 10, titleLabel.getHeight()); titleClip.widthProperty().bind(BindingMapping.of(titleLabel.widthProperty()).map(w -> w.doubleValue() / 2 - 10)); @@ -142,23 +139,7 @@ public TwoLineListItem() { tagLabel.setText(txt); HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); return tagLabel; - }, this::reLayout)); - - // Stable method, but it's quite slow. -// tags.addListener((ListChangeListener) observable -> { -// ArrayList list = new ArrayList<>(tags.size() * 2); -// for (int i = 0; i < 2; i++) { -// for (String tag : tags) { -// Label tagLabel = new Label(); -// tagLabel.getStyleClass().add("tag"); -// tagLabel.setText(tag); -// HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); -// list.add(tagLabel); -// } -// } -// tagBox.getChildren().setAll(list); -// Platform.runLater(this::reLayout); -// }); + })); Rectangle tagClip = new Rectangle(0, 0, tagBox.getWidth() / 2 - 2, tagBox.getHeight()); tagClip.widthProperty().bind(BindingMapping.of(tagBox.widthProperty()).map(w -> w.doubleValue() / 2 - 2)); @@ -173,7 +154,6 @@ public TwoLineListItem() { subTitleLabel = new Label(); subTitleLabel.getStyleClass().add("subtitle"); subTitleLabel.textProperty().bind(BindingMapping.of(subtitle).map(s -> s + " | " + s)); - subtitle.addListener(observable -> Platform.runLater(this::reLayout)); Rectangle subTitleClip = new Rectangle(0, 0, subTitleLabel.getWidth() / 2 - 2, 80000); subTitleClip.widthProperty().bind(BindingMapping.of(subTitleLabel.widthProperty()).map(w -> w.doubleValue() / 2 - 2)); @@ -190,7 +170,6 @@ public TwoLineListItem() { }); getStyleClass().add(DEFAULT_STYLE_CLASS); - this.widthProperty().addListener(observable -> Platform.runLater(this::reLayout)); TranslateTransition titleTranslateTransition = new TranslateTransition(Duration.seconds(0.1D), titleLabel); titleTranslateTransition.setFromX(0); @@ -223,6 +202,8 @@ public TwoLineListItem() { this.minWidthProperty().set(0); HBox.setHgrow(this, Priority.SOMETIMES); + + Lang.immutableListOf(title, tags, subtitle, this.widthProperty()).forEach(o -> o.addListener(observable -> Platform.runLater(this::reLayout))); } private static ChangeListener generateTranslateListener(TranslateTransition translateTransition) { From c9dcc7c515bac05982aa995aa2de8b1ec1ed540f Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 12 Aug 2023 17:36:00 +0800 Subject: [PATCH 048/104] Fix checkstyle --- .../java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 5ed72d0047..6ff05d4207 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -21,7 +21,6 @@ import javafx.animation.Interpolator; import javafx.animation.TranslateTransition; import javafx.application.Platform; -import javafx.beans.InvalidationListener; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; From b1a008cb1b4b04ea4b4b9786ce6aa68847f33a83 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 12 Aug 2023 17:41:05 +0800 Subject: [PATCH 049/104] Set org.jackhuang.hmcl.ui.construct.TwoLineListItem.TagChangeListener as a private static inner class. --- .../java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 6ff05d4207..b2b714b5c3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -53,7 +53,7 @@ public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; private static final double TITLE_PART = 0.7D; - private final class TagChangeListener implements ListChangeListener { + private static final class TagChangeListener implements ListChangeListener { private final List list; private final Function mapper; From 25cf4988bf2e38959405843b74091a7ccb9fcf10 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 12 Aug 2023 18:37:43 +0800 Subject: [PATCH 050/104] Fix --- .../main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 2b9b6c525d..10054fe9b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -319,7 +319,7 @@ protected ModDownloadPageSkin(DownloadPage control) { FXUtils.onChangeAndOperate(control.loaded, loaded -> { if (control.versions == null) return; - if (control.version != null) { + if (control.version != null && control.version.getProfile() != null && control.version.getVersion() != null) { String currentGameVersion = null; for (Version patches : control.version.getProfile().getRepository().getVersion(control.version.getVersion()).getPatches()) { if (patches.getId().equals("game")) { From c1c7404655fe2af4dc57f80705eb3885a76ccc3a Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 13 Aug 2023 12:10:14 +0800 Subject: [PATCH 051/104] Fix --- .../hmcl/ui/versions/DownloadPage.java | 144 +++++++----------- 1 file changed, 59 insertions(+), 85 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 10054fe9b9..99ec647121 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -58,13 +58,10 @@ import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.*; -import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.jackhuang.hmcl.mod.RemoteMod.DependencyType.*; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; -import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class DownloadPage extends Control implements DecoratorPage { @@ -272,32 +269,6 @@ protected ModDownloadPageSkin(DownloadPage control) { runInFX(() -> FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage())); } - if (control.repository.getType() != RemoteModRepository.Type.MOD) { - ComponentList dependencyPane = new ComponentList(Lang::immutableListOf); - dependencyPane.setTitle(i18n("mods.dependency.embedded")); - Task.supplyAsync(() -> control.addon.getData().loadDependencies(control.repository).stream() - .map(remoteMod -> new DependencyModItem(getSkinnable().page, remoteMod, control.version, control.callback)) - .map(dependencyModItem -> { - VBox box = new VBox(); - box.setPadding(new Insets(8, 0, 8, 0)); - box.getChildren().setAll(dependencyModItem); - return box; - }) - .collect(Collectors.toList()) - ).whenComplete(Schedulers.javafx(), (result, exception) -> { - if (exception == null) { - dependencyPane.getContent().setAll(result); - } else { - Label msg = new Label(i18n("download.failed.refresh")); - msg.setPadding(new Insets(8)); - dependencyPane.getContent().setAll(msg); - } - }).start(); - dependencyPane.getStyleClass().add("no-padding"); - - pane.getChildren().addAll(dependencyPane); - } - SpinnerPane spinnerPane = new SpinnerPane(); VBox.setVgrow(spinnerPane, Priority.ALWAYS); pane.getChildren().add(spinnerPane); @@ -372,13 +343,13 @@ protected ModDownloadPageSkin(DownloadPage control) { private static final class DependencyModItem extends StackPane { public static final EnumMap I18N_KEY = new EnumMap<>(Lang.mapOf( - Pair.pair(EMBEDDED, "mods.dependency.embedded"), - Pair.pair(OPTIONAL, "mods.dependency.optional"), - Pair.pair(REQUIRED, "mods.dependency.required"), - Pair.pair(TOOL, "mods.dependency.tool"), - Pair.pair(INCLUDE, "mods.dependency.include"), - Pair.pair(INCOMPATIBLE, "mods.dependency.incompatible"), - Pair.pair(BROKEN, "mods.dependency.broken") + Pair.pair(RemoteMod.DependencyType.EMBEDDED, "mods.dependency.embedded"), + Pair.pair(RemoteMod.DependencyType.OPTIONAL, "mods.dependency.optional"), + Pair.pair(RemoteMod.DependencyType.REQUIRED, "mods.dependency.required"), + Pair.pair(RemoteMod.DependencyType.TOOL, "mods.dependency.tool"), + Pair.pair(RemoteMod.DependencyType.INCLUDE, "mods.dependency.include"), + Pair.pair(RemoteMod.DependencyType.INCOMPATIBLE, "mods.dependency.incompatible"), + Pair.pair(RemoteMod.DependencyType.BROKEN, "mods.dependency.broken") )); DependencyModItem(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, DownloadCallback callback) { @@ -455,13 +426,13 @@ private static final class ModItem extends StackPane { } descPane.getChildren().setAll(graphicPane, content); - descPane.setOnMouseClicked(e -> Controllers.dialog(new ModVersion(dataItem, selfPage))); } pane.getChildren().add(descPane); } RipplerContainer container = new RipplerContainer(pane); + container.setOnMouseClicked(e -> Controllers.dialog(new ModVersion(dataItem, selfPage))); getChildren().setAll(container); // Workaround for https://github.com/huanghongxun/HMCL/issues/2129 @@ -475,56 +446,22 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { VBox box = new VBox(8); box.setPadding(new Insets(8)); - box.getChildren().setAll(new ModItem(version, selfPage)); - if (selfPage.repository.getType() == RemoteModRepository.Type.MOD) { - SpinnerPane spinnerPane = new SpinnerPane(); - ScrollPane scrollPane = new ScrollPane(); - ComponentList dependenciesList = new ComponentList(Lang::immutableListOf); - Task.supplyAsync(() -> { - EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); - for (RemoteMod.Dependency dependency : version.getDependencies()) { - if (dependency.getType() == INCOMPATIBLE || dependency.getType() == BROKEN) { - continue; - } + ModItem modItem = new ModItem(version, selfPage); + modItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + box.getChildren().setAll(modItem); + SpinnerPane spinnerPane = new SpinnerPane(); + ScrollPane scrollPane = new ScrollPane(); + ComponentList dependenciesList = new ComponentList(Lang::immutableListOf); + loadDependencies(version, selfPage, spinnerPane, dependenciesList); + spinnerPane.setOnFailedAction(e -> loadDependencies(version, selfPage, spinnerPane, dependenciesList)); - if (!dependencies.containsKey(dependency.getType())) { - List list = new ArrayList<>(); - Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(dependency.getType()))); - title.setPadding(new Insets(0, 8, 0, 8)); - list.add(title); - dependencies.put(dependency.getType(), list); - } - DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback); - dependencyModItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); - dependencies.get(dependency.getType()).add(dependencyModItem); - } + scrollPane.setContent(dependenciesList); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + spinnerPane.setContent(scrollPane); + box.getChildren().add(spinnerPane); + VBox.setVgrow(spinnerPane, Priority.SOMETIMES); - return dependencies.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); - }).whenComplete(Schedulers.javafx(), (result, exception) -> { - if (exception == null) { - spinnerPane.setLoading(false); - dependenciesList.getContent().setAll(result); - } else { - spinnerPane.setLoading(false); - spinnerPane.setFailedReason(i18n("download.failed.refresh")); - Label msg = new Label(i18n("download.failed.refresh")); - msg.setPadding(new Insets(8)); - LOG.log(Level.WARNING, String.format("Fail to load dependencies of mod %s.", version.getModid()), exception); - dependenciesList.getContent().setAll(msg); - } - }).start(); - spinnerPane.setLoading(true); - - scrollPane.setContent(dependenciesList); - scrollPane.setFitToWidth(true); - scrollPane.setFitToHeight(true); - spinnerPane.setContent(scrollPane); - box.getChildren().add(spinnerPane); - VBox.setVgrow(spinnerPane, Priority.SOMETIMES); - - 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)); - } this.setBody(box); JFXButton downloadButton = new JFXButton(i18n("download")); @@ -540,6 +477,43 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); 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)); + } + + private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage, SpinnerPane spinnerPane, ComponentList dependenciesList) { + spinnerPane.setLoading(true); + Task.supplyAsync(() -> { + EnumMap> dependencies = new EnumMap<>(RemoteMod.DependencyType.class); + for (RemoteMod.Dependency dependency : version.getDependencies()) { + if (dependency.getType() == RemoteMod.DependencyType.INCOMPATIBLE || dependency.getType() == RemoteMod.DependencyType.BROKEN) { + continue; + } + + if (!dependencies.containsKey(dependency.getType())) { + List list = new ArrayList<>(); + Label title = new Label(i18n(DependencyModItem.I18N_KEY.get(dependency.getType()))); + title.setPadding(new Insets(0, 8, 0, 8)); + list.add(title); + dependencies.put(dependency.getType(), list); + } + DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback); + dependencyModItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + dependencies.get(dependency.getType()).add(dependencyModItem); + } + + return dependencies.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + spinnerPane.setLoading(false); + if (exception == null) { + dependenciesList.getContent().setAll(result); + spinnerPane.setFailedReason(null); + } else { + dependenciesList.getContent().setAll(); + spinnerPane.setFailedReason(i18n("download.failed.refresh")); + } + }).start(); } } From 0a8321074801a9c15593309bc7f197d6c0c6ea91 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 13 Aug 2023 13:51:55 +0800 Subject: [PATCH 052/104] Fix --- .../org/jackhuang/hmcl/ui/construct/TwoLineListItem.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index b2b714b5c3..8c627d943e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -53,7 +53,7 @@ public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; private static final double TITLE_PART = 0.7D; - private static final class TagChangeListener implements ListChangeListener { + private final class TagChangeListener implements ListChangeListener { private final List list; private final Function mapper; @@ -85,6 +85,8 @@ public void onChanged(Change change) { } } } + + Platform.runLater(TwoLineListItem.this::layout); } } @@ -202,7 +204,7 @@ public TwoLineListItem() { this.minWidthProperty().set(0); HBox.setHgrow(this, Priority.SOMETIMES); - Lang.immutableListOf(title, tags, subtitle, this.widthProperty()).forEach(o -> o.addListener(observable -> Platform.runLater(this::reLayout))); + Lang.immutableListOf(title, subtitle, this.widthProperty()).forEach(o -> o.addListener(observable -> Platform.runLater(this::reLayout))); } private static ChangeListener generateTranslateListener(TranslateTransition translateTransition) { From d735cd942957810dac0fe7da50a7a76d2e1c9f7e Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Thu, 24 Aug 2023 18:53:05 +0800 Subject: [PATCH 053/104] Enhance SimpleMultimap --- .../hmcl/ui/versions/DownloadPage.java | 10 +++++----- .../jackhuang/hmcl/download/MaintainTask.java | 2 +- .../jackhuang/hmcl/download/VersionList.java | 2 +- .../jackhuang/hmcl/event/EventManager.java | 2 +- .../jackhuang/hmcl/util/SimpleMultimap.java | 20 +++++++++---------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 99ec647121..447722f625 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -77,7 +77,7 @@ public class DownloadPage extends Control implements DecoratorPage { private final DownloadCallback callback; private final DownloadListPage page; - private SimpleMultimap versions; + private SimpleMultimap> versions; public DownloadPage(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) { this.page = page; @@ -123,9 +123,9 @@ private void loadModVersions() { }).start(); } - private SimpleMultimap sortVersions(Stream versions) { - SimpleMultimap classifiedVersions - = new SimpleMultimap(HashMap::new, ArrayList::new); + private SimpleMultimap> sortVersions(Stream versions) { + SimpleMultimap> classifiedVersions + = new SimpleMultimap<>(HashMap::new, ArrayList::new); versions.forEach(version -> { for (String gameVersion : version.getGameVersions()) { classifiedVersions.put(gameVersion, version); @@ -133,7 +133,7 @@ private SimpleMultimap sortVersions(Stream versionList = (List) classifiedVersions.get(gameVersion); + List versionList = classifiedVersions.get(gameVersion); versionList.sort(Comparator.comparing(RemoteMod.Version::getDatePublished).reversed()); } return classifiedVersions; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java index 25b33fba98..e8fe8a1e99 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java @@ -293,7 +293,7 @@ public static boolean isPurePatched(Version version) { public static Version unique(Version version) { List libraries = new ArrayList<>(); - SimpleMultimap multimap = new SimpleMultimap(HashMap::new, ArrayList::new); + SimpleMultimap> multimap = new SimpleMultimap<>(HashMap::new, ArrayList::new); for (Library library : version.getLibraries()) { String id = library.getGroupId() + ":" + library.getArtifactId(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java index a68d1463f6..701380b7d8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java @@ -37,7 +37,7 @@ public abstract class VersionList { * key: game version. * values: corresponding remote versions. */ - protected final SimpleMultimap versions = new SimpleMultimap(HashMap::new, TreeSet::new); + protected final SimpleMultimap> versions = new SimpleMultimap<>(HashMap::new, TreeSet::new); /** * True if the version list has been loaded. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java index dc8d1693a6..d0767fc3da 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java @@ -30,7 +30,7 @@ */ public final class EventManager { - private final SimpleMultimap> handlers + private final SimpleMultimap, CopyOnWriteArraySet>> handlers = new SimpleMultimap<>(() -> new EnumMap<>(EventPriority.class), CopyOnWriteArraySet::new); public Consumer registerWeak(Consumer consumer) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java index 03f4aee30f..4ac8ab06b2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java @@ -28,12 +28,12 @@ * * @author huangyuhui */ -public final class SimpleMultimap { +public final class SimpleMultimap> { - private final Map> map; - private final Supplier> valuer; + private final Map map; + private final Supplier valuer; - public SimpleMultimap(Supplier>> mapper, Supplier> valuer) { + public SimpleMultimap(Supplier> mapper, Supplier valuer) { this.map = mapper.get(); this.valuer = valuer; } @@ -48,7 +48,7 @@ public Set keys() { public Collection values() { Collection res = valuer.get(); - for (Map.Entry> entry : map.entrySet()) + for (Map.Entry entry : map.entrySet()) res.addAll(entry.getValue()); return res; } @@ -61,27 +61,27 @@ public boolean containsKey(K key) { return map.containsKey(key) && !map.get(key).isEmpty(); } - public Collection get(K key) { + public M get(K key) { return map.computeIfAbsent(key, any -> valuer.get()); } public void put(K key, V value) { - Collection set = get(key); + M set = get(key); set.add(value); } public void putAll(K key, Collection value) { - Collection set = get(key); + M set = get(key); set.addAll(value); } - public Collection removeKey(K key) { + public M removeKey(K key) { return map.remove(key); } public boolean removeValue(V value) { boolean flag = false; - for (Collection c : map.values()) + for (M c : map.values()) flag |= c.remove(value); return flag; } From 8e9bf57f68e008638441f5330f4123cbae5b7906 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 26 Aug 2023 18:43:57 +0800 Subject: [PATCH 054/104] Revert TwoLineListItem --- .../hmcl/ui/construct/TwoLineListItem.java | 209 ++---------------- HMCL/src/main/resources/assets/css/root.css | 38 ++-- 2 files changed, 40 insertions(+), 207 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 8c627d943e..19656351a8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -17,98 +17,33 @@ */ package org.jackhuang.hmcl.ui.construct; -import javafx.animation.Animation; -import javafx.animation.Interpolator; -import javafx.animation.TranslateTransition; -import javafx.application.Platform; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import javafx.scene.shape.Rectangle; -import javafx.util.Duration; -import org.jackhuang.hmcl.setting.ConfigHolder; import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.javafx.BindingMapping; - -import java.util.List; -import java.util.function.Function; -import java.util.logging.Level; - -import static org.jackhuang.hmcl.util.Logging.LOG; +import org.jackhuang.hmcl.util.AggregatedObservableList; +import org.jackhuang.hmcl.util.javafx.MappedObservableList; public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; - private static final double TITLE_PART = 0.7D; - - private final class TagChangeListener implements ListChangeListener { - private final List list; - private final Function mapper; - - public TagChangeListener(List list, Function mapper) { - this.list = list; - this.mapper = mapper; - } - - @Override - public void onChanged(Change change) { - if (list != null) { - while (change.next()) { - if (change.wasPermutated()) { - for (int i = change.getFrom(); i < change.getTo(); i++) { - list.set(i, mapper.apply(change.getList().get(i))); - list.set(i + list.size() / 2, mapper.apply(change.getList().get(i))); - } - } else { - if (change.wasRemoved()) { - list.subList(change.getFrom() + list.size() / 2, change.getFrom() + change.getRemovedSize() + list.size() / 2).clear(); - list.subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear(); - } - if (change.wasAdded()) { - for (int i = 0; i < change.getAddedSubList().size(); i++) { - list.add(change.getFrom() + i + list.size() / 2, mapper.apply(change.getAddedSubList().get(i))); - list.add(change.getFrom() + i, mapper.apply(change.getAddedSubList().get(i))); - } - } - } - } - } - - Platform.runLater(TwoLineListItem.this::layout); - } - } private final StringProperty title = new SimpleStringProperty(this, "title"); private final ObservableList tags = FXCollections.observableArrayList(); private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle"); - private final Label titleLabel; - private final HBox tagBox; - private final Label subTitleLabel; - - private final BooleanProperty titleTranslatePlaying = new SimpleBooleanProperty(false); - private final BooleanProperty tagTranslatePlaying = new SimpleBooleanProperty(false); - private final BooleanProperty subTitleTranslatePlaying = new SimpleBooleanProperty(false); - public TwoLineListItem(String titleString, String subtitleString) { this(); title.set(titleString); subtitle.set(subtitleString); - - Platform.runLater(this::reLayout); } public TwoLineListItem() { @@ -117,51 +52,28 @@ public TwoLineListItem() { HBox firstLine = new HBox(); firstLine.getStyleClass().add("first-line"); - { - Pane titlePane = new Pane(); - titlePane.getStyleClass().add("title-pane"); - - titleLabel = new Label(); - titleLabel.getStyleClass().add("title"); - titleLabel.textProperty().bind(BindingMapping.of(title).map(s -> s + " | " + s)); - - Rectangle titleClip = new Rectangle(0, 0, titleLabel.getWidth() / 2 - 10, titleLabel.getHeight()); - titleClip.widthProperty().bind(BindingMapping.of(titleLabel.widthProperty()).map(w -> w.doubleValue() / 2 - 10)); - titlePane.setClip(titleClip); - titlePane.getChildren().setAll(titleLabel); - - Pane tagLabelsPane = new Pane(); - tagLabelsPane.getStyleClass().add("tag-pane"); - - tagBox = new HBox(); - tags.addListener(new TagChangeListener(tagBox.getChildren(), txt -> { - Label tagLabel = new Label(); - tagLabel.getStyleClass().add("tag"); - tagLabel.setText(txt); - HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); - return tagLabel; - })); - - Rectangle tagClip = new Rectangle(0, 0, tagBox.getWidth() / 2 - 2, tagBox.getHeight()); - tagClip.widthProperty().bind(BindingMapping.of(tagBox.widthProperty()).map(w -> w.doubleValue() / 2 - 2)); - tagLabelsPane.setClip(tagClip); - tagLabelsPane.getChildren().setAll(tagBox); - - firstLine.getChildren().setAll(titlePane, tagLabelsPane); - } + Label lblTitle = new Label(); + lblTitle.getStyleClass().add("title"); + lblTitle.textProperty().bind(title); - Pane secondLine = new Pane(); - { - subTitleLabel = new Label(); - subTitleLabel.getStyleClass().add("subtitle"); - subTitleLabel.textProperty().bind(BindingMapping.of(subtitle).map(s -> s + " | " + s)); + ObservableList tagLabels = MappedObservableList.create(tags, tag -> { + Label tagLabel = new Label(); + tagLabel.getStyleClass().add("tag"); + tagLabel.setText(tag); + HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0)); + return tagLabel; + }); + AggregatedObservableList firstLineChildren = new AggregatedObservableList<>(); + firstLineChildren.appendList(FXCollections.singletonObservableList(lblTitle)); + firstLineChildren.appendList(tagLabels); + Bindings.bindContent(firstLine.getChildren(), firstLineChildren.getAggregatedList()); - Rectangle subTitleClip = new Rectangle(0, 0, subTitleLabel.getWidth() / 2 - 2, 80000); - subTitleClip.widthProperty().bind(BindingMapping.of(subTitleLabel.widthProperty()).map(w -> w.doubleValue() / 2 - 2)); - secondLine.setClip(subTitleClip); + Label lblSubtitle = new Label(); + lblSubtitle.getStyleClass().add("subtitle"); + lblSubtitle.textProperty().bind(subtitle); - secondLine.getChildren().setAll(subTitleLabel); - } + HBox secondLine = new HBox(); + secondLine.getChildren().setAll(lblSubtitle); getChildren().setAll(firstLine, secondLine); @@ -172,83 +84,8 @@ public TwoLineListItem() { getStyleClass().add(DEFAULT_STYLE_CLASS); - TranslateTransition titleTranslateTransition = new TranslateTransition(Duration.seconds(0.1D), titleLabel); - titleTranslateTransition.setFromX(0); - titleTranslateTransition.durationProperty().bind(BindingMapping.of(titleLabel.widthProperty()).map(w -> Duration.seconds((w.doubleValue() / 2D + 1) / 30D))); - titleTranslateTransition.toXProperty().bind(BindingMapping.of(titleLabel.widthProperty()).map(w -> -w.doubleValue() / 2D - 1)); - titleTranslateTransition.setInterpolator(Interpolator.LINEAR); - titleTranslateTransition.setCycleCount(Animation.INDEFINITE); - titleTranslatePlaying.addListener(generateTranslateListener(titleTranslateTransition)); - - TranslateTransition tagTranslateTransition = new TranslateTransition(Duration.seconds(0.1D), tagBox); - tagTranslateTransition.setFromX(0); - tagTranslateTransition.durationProperty().bind(BindingMapping.of(tagBox.widthProperty()).map(w -> Duration.seconds(w.doubleValue() / 2D / 30D))); - tagTranslateTransition.toXProperty().bind(BindingMapping.of(tagBox.widthProperty()).map(w -> -w.doubleValue() / 2D)); - tagTranslateTransition.setInterpolator(Interpolator.LINEAR); - tagTranslateTransition.setCycleCount(Animation.INDEFINITE); - tagTranslatePlaying.addListener(generateTranslateListener(tagTranslateTransition)); - - TranslateTransition subTitleTranslateTransition = new TranslateTransition(Duration.seconds(0.1D), subTitleLabel); - subTitleTranslateTransition.setFromX(0); - subTitleTranslateTransition.durationProperty().bind(BindingMapping.of(subTitleLabel.widthProperty()).map(w -> Duration.seconds(w.doubleValue() / 2D / 30D))); - subTitleTranslateTransition.toXProperty().bind(BindingMapping.of(subTitleLabel.widthProperty()).map(w -> (-w.doubleValue() - 8) / 2D)); - subTitleTranslateTransition.setInterpolator(Interpolator.LINEAR); - subTitleTranslateTransition.setCycleCount(Animation.INDEFINITE); - subTitleTranslatePlaying.addListener(generateTranslateListener(subTitleTranslateTransition)); - - Rectangle mainClip = new Rectangle(0, 0, this.getWidth(), this.getHeight()); - mainClip.widthProperty().bind(this.widthProperty()); - mainClip.heightProperty().bind(this.heightProperty()); - this.setClip(mainClip); - this.minWidthProperty().set(0); HBox.setHgrow(this, Priority.SOMETIMES); - - Lang.immutableListOf(title, subtitle, this.widthProperty()).forEach(o -> o.addListener(observable -> Platform.runLater(this::reLayout))); - } - - private static ChangeListener generateTranslateListener(TranslateTransition translateTransition) { - if (ConfigHolder.config().isAnimationDisabled()) { - return (observable, oldValue, newValue) -> { - }; - } else { - return (observable, oldValue, newValue) -> { - if (oldValue.booleanValue() != newValue.booleanValue()) { - if (newValue) { - translateTransition.play(); - } else { - translateTransition.jumpTo(Duration.ZERO); - translateTransition.stop(); - } - } - }; - } - } - - private void reLayout() { - try { - this.layout(); - } catch (NullPointerException npe) { - LOG.log(Level.WARNING, "For some unknown reasons, invoking this::layout too fast may produce NullPointerException.", npe); - } - - double titleMaxWidth = tags.size() == 0 ? this.getWidth() : this.getWidth() * TITLE_PART; - double titleWidth = Math.min(titleLabel.getWidth() / 2, titleMaxWidth); - double tagMaxWidth = this.getWidth() - titleWidth - this.paddingProperty().get().getLeft() - this.paddingProperty().get().getRight() - 5; - double tagWidth = Math.min(tagBox.getWidth() / 2, tagMaxWidth); - - FXUtils.setLimitWidth((Pane) titleLabel.getParent(), titleWidth); - Rectangle titleClip = new Rectangle(0, 0, titleWidth - 7, titleLabel.getHeight()); - titleLabel.getParent().setClip(titleClip); - - titleTranslatePlaying.set(titleLabel.getWidth() / 2 > titleMaxWidth); - - Rectangle tagClip = new Rectangle(0, 0, tagWidth - 2, tagBox.getHeight()); - tagBox.getParent().setClip(tagClip); - - tagTranslatePlaying.set(tagBox.getWidth() / 2 > tagMaxWidth); - - subTitleTranslatePlaying.set(subTitleLabel.getWidth() / 2 > this.getWidth()); } public String getTitle() { @@ -283,4 +120,4 @@ public ObservableList getTags() { public String toString() { return getTitle(); } -} +} \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index c21b81abca..aea759d2a9 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -155,12 +155,12 @@ -fx-padding: 0 0 0 10; } -.advanced-list-item > .rippler-container > .container > .two-line-list-item > .first-line > .title-pane > .title { +.advanced-list-item > .rippler-container > .container > .two-line-list-item > .first-line > .title { -fx-font-size: 13; -fx-text-alignment: justify; } -.advanced-list-item > .rippler-container > .container > .two-line-list-item > Pane > .subtitle { +.advanced-list-item > .rippler-container > .container > .two-line-list-item > HBox > .subtitle { -fx-font-size: 10; -fx-text-alignment: justify; } @@ -169,7 +169,7 @@ -fx-background-color: -fx-base-rippler-color; } -.advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > .first-line > .title-pane > .title { +.advanced-list-item:selected > .rippler-container > .container > .two-line-list-item > .first-line > .title { -fx-text-fill: -fx-base-color; -fx-font-weight: bold; } @@ -182,11 +182,11 @@ -fx-padding: 0 0 0 0; } -.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title-pane > .title { +.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title { -fx-font-size: 13; } -.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > Pane > .subtitle { +.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > HBox > .subtitle { -fx-font-size: 10; } @@ -198,7 +198,7 @@ -fx-background-color: -fx-base-rippler-color; } -.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title-pane > .title { +.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title { -fx-text-fill: -fx-base-color; -fx-font-weight: bold; } @@ -261,26 +261,22 @@ -fx-padding: 4 0 4 0; } -.two-line-list-item > .first-line > .title-pane { - -fx-overflow: hidden; -} - -.two-line-list-item > .first-line > .title-pane > .title { +.two-line-list-item > .first-line > .title { -fx-text-fill: #292929; -fx-font-size: 15px; -fx-padding: 0 8 0 0; } -.two-line-list-item > .first-line > .tag-pane > HBox > .tag { - -fx-text-fill: -fx-base-color; - -fx-background-color: -fx-base-rippler-color; - -fx-padding: 2; +.two-line-list-item > HBox > .subtitle { + -fx-text-fill: rgba(0, 0, 0, 0.5); -fx-font-weight: normal; -fx-font-size: 12px; } -.two-line-list-item > Pane > .subtitle { - -fx-text-fill: rgba(0, 0, 0, 0.5); +.two-line-list-item > .first-line > .tag { + -fx-text-fill: -fx-base-color; + -fx-background-color: -fx-base-rippler-color; + -fx-padding: 2; -fx-font-weight: normal; -fx-font-size: 12px; } @@ -289,13 +285,13 @@ } -.two-line-item-second-large > .first-line > .title-pane > .title, .two-line-item-second-large-title { +.two-line-item-second-large > .first-line > .title, .two-line-item-second-large-title { -fx-text-fill: rgba(0, 0, 0, 0.5); -fx-font-weight: normal; -fx-font-size: 12px; } -.two-line-item-second-large > Pane > .subtitle { +.two-line-item-second-large > HBox > .subtitle { -fx-text-fill: #292929; -fx-font-size: 15px; } @@ -310,8 +306,8 @@ -fx-text-fill: white; } -.bubble > HBox > .two-line-list-item > .first-line > .title-pane > .title, -.bubble > HBox > .two-line-list-item > Pane > .subtitle { +.bubble > HBox > .two-line-list-item > .first-line > .title, +.bubble > HBox > .two-line-list-item > HBox > .subtitle { -fx-text-fill: white; } From 3a4a64a9c953cd0437e5e68cecbe0e59ea9f469d Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 26 Aug 2023 19:52:31 +0800 Subject: [PATCH 055/104] Fix --- .../hmcl/ui/construct/TwoLineListItem.java | 2 +- .../hmcl/ui/versions/DownloadPage.java | 34 ++++++++---------- .../hmcl/download/LibraryAnalyzer.java | 36 +++++++++++++------ 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 19656351a8..c0f6929cd5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -120,4 +120,4 @@ public ObservableList getTags() { public String toString() { return getTitle(); } -} \ No newline at end of file +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 447722f625..bad242f492 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -31,6 +31,7 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.stage.FileChooser; +import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.mod.ModManager; @@ -292,33 +293,26 @@ protected ModDownloadPageSkin(DownloadPage control) { if (control.version != null && control.version.getProfile() != null && control.version.getVersion() != null) { String currentGameVersion = null; - for (Version patches : control.version.getProfile().getRepository().getVersion(control.version.getVersion()).getPatches()) { + Version game = control.version.getProfile().getRepository().getVersion(control.version.getVersion()); + for (Version patches : game.getPatches()) { if (patches.getId().equals("game")) { currentGameVersion = patches.getVersion(); break; } } - if (currentGameVersion != null && control.versions.containsKey(currentGameVersion) && control.versions.get(currentGameVersion).stream().findFirst().isPresent()) { - list.getContent().addAll( - ComponentList.createComponentListTitle(i18n("mods.download.recommend", currentGameVersion)), - new ModItem(control.versions.get(currentGameVersion).stream().findFirst().get(), control) - ); - - for (String gameVersion : control.versions.keys().stream() - .sorted(VersionNumber.VERSION_COMPARATOR.reversed()) - .collect(Collectors.toList())) { - ComponentList sublist = new ComponentList(() -> - control.versions.get(gameVersion).stream() - .map(version -> new ModItem(version, control)) - .collect(Collectors.toList())); - sublist.getStyleClass().add("no-padding"); - sublist.setTitle(gameVersion); - - list.getContent().add(sublist); - } - return; + if (currentGameVersion != null) { + Set currentGameModLoaders = LibraryAnalyzer.analyze(game).getModLoaders(); + Optional recommendVersion = control.versions.get(currentGameVersion).stream() + .filter(version -> version.getLoaders().isEmpty() || version.getLoaders().stream().anyMatch(currentGameModLoaders::contains)) + .findFirst(); + if (recommendVersion.isPresent()) { + list.getContent().addAll( + ComponentList.createComponentListTitle(i18n("mods.download.recommend", currentGameVersion)), + new ModItem(recommendVersion.get(), control) + ); + } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index bca60404b4..e1d78f0f2e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -20,6 +20,7 @@ import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.VersionProvider; +import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.util.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -161,11 +162,20 @@ public static boolean isModded(VersionProvider provider, Version version) { || mainClass.startsWith("cpw.mods")); } + public Set getModLoaders() { + return Arrays.stream(LibraryType.values()) + .filter(LibraryType::isModLoader) + .filter(this::has) + .map(LibraryType::getModLoaderType) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + public enum LibraryType { - MINECRAFT(true, "game", Pattern.compile("^$"), Pattern.compile("^$")), - FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")), - FABRIC_API(true, "fabric-api", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-api")), - FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("(forge|fmlloader)")) { + MINECRAFT(true, "game", Pattern.compile("^$"), Pattern.compile("^$"), null), + FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader"), ModLoaderType.FABRIC), + FABRIC_API(true, "fabric-api", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-api"), null), + FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("(forge|fmlloader)"), ModLoaderType.FORGE) { private final Pattern FORGE_VERSION_MATCHER = Pattern.compile("^([0-9.]+)-(?[0-9.]+)(-([0-9.]+))?$"); @Override @@ -177,21 +187,23 @@ public String patchVersion(String libraryVersion) { return super.patchVersion(libraryVersion); } }, - LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")), - OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$")), - QUILT(true, "quilt", Pattern.compile("org\\.quiltmc"), Pattern.compile("quilt-loader")), - QUILT_API(true, "quilt-api", Pattern.compile("org\\.quiltmc"), Pattern.compile("quilt-api")), - BOOTSTRAP_LAUNCHER(false, "", Pattern.compile("cpw\\.mods"), Pattern.compile("bootstraplauncher")); + LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader"), ModLoaderType.LITE_LOADER), + OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$"), null), + QUILT(true, "quilt", Pattern.compile("org\\.quiltmc"), Pattern.compile("quilt-loader"), ModLoaderType.QUILT), + QUILT_API(true, "quilt-api", Pattern.compile("org\\.quiltmc"), Pattern.compile("quilt-api"), null), + BOOTSTRAP_LAUNCHER(false, "", Pattern.compile("cpw\\.mods"), Pattern.compile("bootstraplauncher"), null); private final boolean modLoader; private final String patchId; private final Pattern group, artifact; + private final ModLoaderType modLoaderType; - LibraryType(boolean modLoader, String patchId, Pattern group, Pattern artifact) { + LibraryType(boolean modLoader, String patchId, Pattern group, Pattern artifact, ModLoaderType modLoaderType) { this.modLoader = modLoader; this.patchId = patchId; this.group = group; this.artifact = artifact; + this.modLoaderType = modLoaderType; } public boolean isModLoader() { @@ -202,6 +214,10 @@ public String getPatchId() { return patchId; } + public ModLoaderType getModLoaderType() { + return modLoaderType; + } + public static LibraryType fromPatchId(String patchId) { for (LibraryType type : values()) if (type.getPatchId().equals(patchId)) From 67768569c9db638b6646e230b651d1847a625e87 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 27 Aug 2023 10:42:01 +0800 Subject: [PATCH 056/104] Code cleanup --- .../java/org/jackhuang/hmcl/ui/versions/DownloadPage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index bad242f492..893facbdea 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -291,7 +291,7 @@ protected ModDownloadPageSkin(DownloadPage control) { FXUtils.onChangeAndOperate(control.loaded, loaded -> { if (control.versions == null) return; - if (control.version != null && control.version.getProfile() != null && control.version.getVersion() != null) { + if (control.version.getProfile() != null && control.version.getVersion() != null) { String currentGameVersion = null; Version game = control.version.getProfile().getRepository().getVersion(control.version.getVersion()); for (Version patches : game.getPatches()) { @@ -385,7 +385,7 @@ private static final class ModItem extends StackPane { { StackPane graphicPane = new StackPane(); - graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24)); + graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24)); TwoLineListItem content = new TwoLineListItem(); HBox.setHgrow(content, Priority.ALWAYS); From 0fb4ea295a9a0a3103a62daae0737f0613da8ecf Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 27 Aug 2023 10:59:50 +0800 Subject: [PATCH 057/104] Code cleanup --- .../hmcl/ui/versions/DownloadPage.java | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 893facbdea..9e3579b071 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -292,28 +292,18 @@ protected ModDownloadPageSkin(DownloadPage control) { if (control.versions == null) return; if (control.version.getProfile() != null && control.version.getVersion() != null) { - String currentGameVersion = null; Version game = control.version.getProfile().getRepository().getVersion(control.version.getVersion()); - for (Version patches : game.getPatches()) { - if (patches.getId().equals("game")) { - currentGameVersion = patches.getVersion(); - break; - } - } - - - if (currentGameVersion != null) { - Set currentGameModLoaders = LibraryAnalyzer.analyze(game).getModLoaders(); - Optional recommendVersion = control.versions.get(currentGameVersion).stream() - .filter(version -> version.getLoaders().isEmpty() || version.getLoaders().stream().anyMatch(currentGameModLoaders::contains)) - .findFirst(); - if (recommendVersion.isPresent()) { - list.getContent().addAll( - ComponentList.createComponentListTitle(i18n("mods.download.recommend", currentGameVersion)), - new ModItem(recommendVersion.get(), control) - ); - } - } + LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(game); + libraryAnalyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT).ifPresent(currentGameVersion -> { + Set currentGameModLoaders = libraryAnalyzer.getModLoaders(); + control.versions.get(currentGameVersion).stream() + .filter(version1 -> version1.getLoaders().isEmpty() || version1.getLoaders().stream().anyMatch(currentGameModLoaders::contains)) + .findFirst() + .ifPresent(value -> list.getContent().addAll( + ComponentList.createComponentListTitle(i18n("mods.download.recommend", currentGameVersion)), + new ModItem(value, control) + )); + }); } for (String gameVersion : control.versions.keys().stream() From adee9a673cb56188eb99d46c3ebdc53ffde4258e Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 27 Aug 2023 16:13:52 +0800 Subject: [PATCH 058/104] Fix --- .../hmcl/ui/versions/DownloadPage.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 9e3579b071..74545183f4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -292,17 +292,19 @@ protected ModDownloadPageSkin(DownloadPage control) { if (control.versions == null) return; if (control.version.getProfile() != null && control.version.getVersion() != null) { - Version game = control.version.getProfile().getRepository().getVersion(control.version.getVersion()); + Version game = control.version.getProfile().getRepository().getResolvedPreservingPatchesVersion(control.version.getVersion()); LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(game); libraryAnalyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT).ifPresent(currentGameVersion -> { Set currentGameModLoaders = libraryAnalyzer.getModLoaders(); - control.versions.get(currentGameVersion).stream() - .filter(version1 -> version1.getLoaders().isEmpty() || version1.getLoaders().stream().anyMatch(currentGameModLoaders::contains)) - .findFirst() - .ifPresent(value -> list.getContent().addAll( - ComponentList.createComponentListTitle(i18n("mods.download.recommend", currentGameVersion)), - new ModItem(value, control) - )); + if (control.versions.containsKey(currentGameVersion)) { + control.versions.get(currentGameVersion).stream() + .filter(version1 -> version1.getLoaders().isEmpty() || version1.getLoaders().stream().anyMatch(currentGameModLoaders::contains)) + .findFirst() + .ifPresent(value -> list.getContent().addAll( + ComponentList.createComponentListTitle(i18n("mods.download.recommend", currentGameVersion)), + new ModItem(value, control) + )); + } }); } From 9db9e9f8fed60bb39748f4338c55bd32aa498914 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Mon, 28 Aug 2023 22:55:24 +0800 Subject: [PATCH 059/104] Code cleanup --- .../main/java/org/jackhuang/hmcl/mod/ModManager.java | 10 +++++----- .../hmcl/mod/modinfo/IModMetadataReader.java | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 3abe43a46b..005e604415 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -29,17 +29,17 @@ import java.util.*; public final class ModManager { - private final Map readers = IModMetadataReader.ofStorage( - IModMetadataReader.MetadataReaderStorage.ofExtensions("zip", "jar").ofReaders( + private final Map readers = IModMetadataReader.ofStorages( + IModMetadataReader.ofExtensions("zip", "jar").ofReaders( new ForgeOldModMetadata(), new ForgeNewModMetadata(), new FabricModMetadata(), new QuiltModMetadata(), new PackMcMeta() - ).ofDesc(""), - IModMetadataReader.MetadataReaderStorage.ofExtensions("litemod").ofReaders( + ).ofDefaultDesc(""), + IModMetadataReader.ofExtensions("litemod").ofReaders( new LiteModMetadata() - ).ofDesc("LiteLoader Mod") + ).ofDefaultDesc("LiteLoader Mod") ); private final GameRepository repository; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java index 67805f28e4..8de40d7437 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java @@ -13,7 +13,7 @@ public interface IModMetadataReader { LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException; - static Map ofStorage(MetadataReaderStorage... storages) { + static Map ofStorages(MetadataReaderStorage... storages) { Map storageMap = new HashMap<>(); for (MetadataReaderStorage storage : storages) { if (storage.extensions == null || storage.defaultDesc == null) { @@ -27,6 +27,10 @@ static Map ofStorage(MetadataReaderStorage... sto return storageMap; } + static MetadataReaderStorage ofExtensions(String... extensions) { + return new MetadataReaderStorage(extensions); + } + final class MetadataReaderStorage { private String[] extensions; @@ -38,16 +42,12 @@ private MetadataReaderStorage(String... extensions) { this.extensions = extensions; } - public static MetadataReaderStorage ofExtensions(String... extensions) { - return new MetadataReaderStorage(extensions); - } - public MetadataReaderStorage ofReaders(IModMetadataReader... readers) { this.readers = readers; return this; } - public MetadataReaderStorage ofDesc(String defaultDesc) { + public MetadataReaderStorage ofDefaultDesc(String defaultDesc) { this.defaultDesc = defaultDesc; return this; } From a043057124cf720aa2b76fe73052ddc4c2a17ec7 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Mon, 28 Aug 2023 22:55:58 +0800 Subject: [PATCH 060/104] Add license for IModMetadataReader --- .../hmcl/mod/modinfo/IModMetadataReader.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java index 8de40d7437..1ca1c2ce79 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.mod.modinfo; import com.google.gson.JsonParseException; From 021d06201f8c135a5020e1084ad2e6730037b69d Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 29 Aug 2023 19:46:03 +0800 Subject: [PATCH 061/104] Add prefix 'Minecraft' at the supported minecrft version list in DownloadPage --- .../main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java | 2 +- HMCL/src/main/resources/assets/lang/I18N.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 74545183f4..551f7f8917 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -316,7 +316,7 @@ protected ModDownloadPageSkin(DownloadPage control) { .map(version -> new ModItem(version, control)) .collect(Collectors.toList())); sublist.getStyleClass().add("no-padding"); - sublist.setTitle(gameVersion); + sublist.setTitle("Minecraft " + gameVersion); list.getContent().add(sublist); } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index d103189ae8..33b453d92b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -882,7 +882,7 @@ mods.dependency.broken=Broken pre-mod (This premod used to exist on the mod repo mods.disable=Disable mods.download=Mod Download mods.download.title=Mod Download - %1s -mods.download.recommend=Recommended Mod Version - %1s +mods.download.recommend=Recommended Mod Version - Minecraft %1s mods.enable=Enable mods.manage=Manage Mods mods.mcbbs=MCBBS diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 502078d11c..f203693b40 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -752,7 +752,7 @@ mods.dependency.broken=損壞的前綴(這個前綴曾經存在於 mod 倉庫 mods.disable=停用 mods.download=模組下載 mods.download.title=模組下載 - %1s -mods.download.recommend=推薦版本 - %1s +mods.download.recommend=推薦版本 - Minecraft %1s mods.enable=啟用 mods.manage=模組管理 mods.mcbbs=MCBBS diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 0c02158ba8..721ce6e37c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -751,7 +751,7 @@ mods.dependency.broken=损坏的前置模组(该前置模组曾经在该模组 mods.disable=禁用 mods.download=模组下载 mods.download.title=模组下载 - %1s -mods.download.recommend=推荐版本 - %1s +mods.download.recommend=推荐版本 - Minecraft %1s mods.enable=启用 mods.manage=模组管理 mods.mcbbs=MCBBS From e9aed5512cbfb3927b9e76503d2ad79cc72d6e30 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 10 Sep 2023 19:56:11 +0800 Subject: [PATCH 062/104] Fix #2498 --- .../main/java/org/jackhuang/hmcl/util/io/HttpRequest.java | 2 +- .../src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java | 5 +++++ .../main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java index 5414a7c405..c15b166678 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java @@ -142,7 +142,7 @@ public String getString() throws IOException { return getStringWithRetry(() -> { HttpURLConnection con = createConnection(); con = resolveConnection(con); - return IOUtils.readFullyAsString(con.getInputStream()); + return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(con.getInputStream()) : con.getInputStream()); }, retryTimes); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java index 3b926719a0..76d07783b6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.util.io; import java.io.*; +import java.util.zip.GZIPInputStream; /** * This utility class consists of some util methods operating on InputStream/OutputStream. @@ -79,4 +80,8 @@ public static void copyTo(InputStream src, OutputStream dest, byte[] buf) throws dest.write(buf, 0, len); } } + + public static InputStream wrapFromGZip(InputStream inputStream) throws IOException { + return new GZIPInputStream(inputStream); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java index f16ba9dedf..9db3fbc7c0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java @@ -209,13 +209,13 @@ public static String doPost(URL url, String post, String contentType) throws IOE public static String readData(HttpURLConnection con) throws IOException { try { try (InputStream stdout = con.getInputStream()) { - return IOUtils.readFullyAsString(stdout); + return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(stdout) : stdout); } } catch (IOException e) { try (InputStream stderr = con.getErrorStream()) { if (stderr == null) throw e; - return IOUtils.readFullyAsString(stderr); + return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(stderr) : stderr); } } } From 7566b6a299a99f8982f58e012ad8753637f66d12 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 17 Sep 2023 14:00:42 +0800 Subject: [PATCH 063/104] Update README_cn.md --- README_cn.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/README_cn.md b/README_cn.md index c7762e1472..6738770ab6 100644 --- a/README_cn.md +++ b/README_cn.md @@ -48,16 +48,18 @@ HMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS 请确保您至少安装了含有 JavaFX 8 的 Java. 建议使用 Liberica Full JDK 8 或更高版本. ## JVM 选项 (用于调试) -| 参数 | 简介 | -|----------------------------------------------|-------------------------------------------------------------------------------------------------| -| `-Dhmcl.home=` | 覆盖 HMCL 数据文件夹. | -| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时绕过本体完整性检查. | -| `-Dhmcl.bmclapi.override=` | 覆盖 BMCLAPI 的 API Root, 默认值为 `https://bmclapi2.bangbang93.com`. 例如 `https://download.mcbbs.net`. | -| `-Dhmcl.font.override=` | 覆盖字族. | -| `-Dhmcl.version.override=` | 覆盖版本号. | -| `-Dhmcl.update_source.override=` | 覆盖更新源. | -| `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下载一个). | -| `-Dhmcl.openjfx.repo=` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 | -| `-Dhmcl.native.encoding=` | 覆盖原生编码. | -| `-Dhmcl.microsoft.auth.id=` | 覆盖 Microsoft OAuth App ID. | -| `-Dhmcl.microsoft.auth.secret=` | 覆盖 Microsoft OAuth App 密钥. | +| 参数 | 简介 | +|------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| `-Dhmcl.home=` | 覆盖 HMCL 数据文件夹. | +| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时绕过本体完整性检查. | +| `-Dhmcl.bmclapi.override=` | 覆盖 BMCLAPI 的 API Root, 默认值为 `https://bmclapi2.bangbang93.com`. 例如 `https://download.mcbbs.net`. | +| `-Dhmcl.font.override=` | 覆盖字族. | +| `-Dhmcl.version.override=` | 覆盖版本号. | +| ~~`-Dhmcl.update_source.override=`~~ | 覆盖 HMCL 更新源(已弃用,请使用 `hmcl.hmcl_update_source.override`). | +| `-Dhmcl.hmcl_update_source.override=` | 覆盖 HMCL 更新源. | +| `-Dhmcl.resource_update_source.override=` | 覆盖动态远程资源更新源. | +| `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下载一个). | +| `-Dhmcl.openjfx.repo=` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 | +| `-Dhmcl.native.encoding=` | 覆盖原生编码. | +| `-Dhmcl.microsoft.auth.id=` | 覆盖 Microsoft OAuth App ID. | +| `-Dhmcl.microsoft.auth.secret=` | 覆盖 Microsoft OAuth App 密钥. | From 61d8add4859e8a0431323cbd3a22dd335bcadb46 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Mon, 18 Sep 2023 18:49:19 +0800 Subject: [PATCH 064/104] Opti ModMananger --- .../org/jackhuang/hmcl/mod/ModManager.java | 48 +++++++---- .../hmcl/mod/modinfo/FabricModMetadata.java | 4 +- .../hmcl/mod/modinfo/ForgeNewModMetadata.java | 4 +- .../hmcl/mod/modinfo/ForgeOldModMetadata.java | 4 +- .../hmcl/mod/modinfo/IModMetadataReader.java | 80 ------------------- .../hmcl/mod/modinfo/LiteModMetadata.java | 4 +- .../hmcl/mod/modinfo/PackMcMeta.java | 4 +- .../hmcl/mod/modinfo/QuiltModMetadata.java | 10 +-- 8 files changed, 48 insertions(+), 110 deletions(-) delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 005e604415..16c207c146 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -17,8 +17,10 @@ */ package org.jackhuang.hmcl.mod; +import com.google.gson.JsonParseException; import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.mod.modinfo.*; +import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -29,18 +31,28 @@ import java.util.*; public final class ModManager { - private final Map readers = IModMetadataReader.ofStorages( - IModMetadataReader.ofExtensions("zip", "jar").ofReaders( - new ForgeOldModMetadata(), - new ForgeNewModMetadata(), - new FabricModMetadata(), - new QuiltModMetadata(), - new PackMcMeta() - ).ofDefaultDesc(""), - IModMetadataReader.ofExtensions("litemod").ofReaders( - new LiteModMetadata() - ).ofDefaultDesc("LiteLoader Mod") - ); + @FunctionalInterface + private interface ModMetadataReader { + LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException; + } + + private static final Map> READERS; + + static { + TreeMap> readers = new TreeMap<>(); + readers.put("zip", Pair.pair(new ModMetadataReader[]{ + ForgeOldModMetadata::fromFile, + ForgeNewModMetadata::fromFile, + FabricModMetadata::fromFile, + QuiltModMetadata::fromFile, + PackMcMeta::fromFile, + }, "")); + readers.put("jar", readers.get("zip")); + readers.put("litemod", Pair.pair(new ModMetadataReader[]{ + LiteModMetadata::fromFile + }, "LiteLoader Mod")); + READERS = Collections.unmodifiableMap(readers); + } private final GameRepository repository; private final String id; @@ -83,12 +95,13 @@ private void addModInfo(Path file) { public LocalModFile getModInfo(Path modFile) { String fileName = StringUtils.removeSuffix(FileUtils.getName(modFile), DISABLED_EXTENSION, OLD_EXTENSION); String extension = fileName.substring(fileName.lastIndexOf(".") + 1); - if (!readers.containsKey(extension)) { + Pair currentReader = READERS.get(extension); + if (currentReader == null) { throw new IllegalArgumentException("File " + modFile + " is not a mod file."); } try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile)) { - for (IModMetadataReader reader : readers.get(extension).getReaders()) { + for (ModMetadataReader reader : currentReader.getKey()) { try { return reader.fromFile(this, modFile, fs); } catch (Exception ignore) { @@ -101,7 +114,7 @@ public LocalModFile getModInfo(Path modFile) { getLocalMod(FileUtils.getNameWithoutExtension(modFile), ModLoaderType.UNKNOWN), modFile, FileUtils.getNameWithoutExtension(modFile), - new LocalModFile.Description(readers.get(extension).getDefaultDesc()) + new LocalModFile.Description(currentReader.getValue()) ); } @@ -273,6 +286,11 @@ public static boolean isFileMod(Path modFile) { return true; } + if (Files.exists(fs.getPath("quilt.mod.json"))) { + // Quilt mod + return true; + } + if (Files.exists(fs.getPath("litemod.json"))) { // Liteloader mod return true; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java index 95049b9d73..9b0a721b32 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java @@ -37,7 +37,7 @@ import java.util.stream.Collectors; @Immutable -public final class FabricModMetadata implements IModMetadataReader { +public final class FabricModMetadata { private final String id; private final String name; private final String version; @@ -60,7 +60,7 @@ public FabricModMetadata(String id, String name, String version, String icon, St this.contact = contact; } - public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path mcmod = fs.getPath("fabric.mod.json"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a Fabric mod."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java index 28fa252586..5d3b343773 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java @@ -22,7 +22,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG; @Immutable -public final class ForgeNewModMetadata implements IModMetadataReader { +public final class ForgeNewModMetadata { private final String modLoader; private final String loaderVersion; @@ -117,7 +117,7 @@ public String getDescription() { } } - public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path modstoml = fs.getPath("META-INF/mods.toml"); if (Files.notExists(modstoml)) throw new IOException("File " + modFile + " is not a Forge 1.13+ mod."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java index 9472057526..067c5b8c25 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java @@ -39,7 +39,7 @@ * @author huangyuhui */ @Immutable -public final class ForgeOldModMetadata implements IModMetadataReader { +public final class ForgeOldModMetadata { @SerializedName("modid") private final String modId; private final String name; @@ -121,7 +121,7 @@ public String[] getAuthors() { return authors; } - public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path mcmod = fs.getPath("mcmod.info"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a Forge mod."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java deleted file mode 100644 index 1ca1c2ce79..0000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/IModMetadataReader.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.mod.modinfo; - -import com.google.gson.JsonParseException; -import org.jackhuang.hmcl.mod.LocalModFile; -import org.jackhuang.hmcl.mod.ModManager; - -import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; - -public interface IModMetadataReader { - LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException; - - static Map ofStorages(MetadataReaderStorage... storages) { - Map storageMap = new HashMap<>(); - for (MetadataReaderStorage storage : storages) { - if (storage.extensions == null || storage.defaultDesc == null) { - throw new IllegalArgumentException(); - } - for (String extension : storage.extensions) { - storageMap.put(extension, storage); - } - storage.extensions = null; - } - return storageMap; - } - - static MetadataReaderStorage ofExtensions(String... extensions) { - return new MetadataReaderStorage(extensions); - } - - final class MetadataReaderStorage { - private String[] extensions; - - private IModMetadataReader[] readers = new IModMetadataReader[0]; - - private String defaultDesc = null; - - private MetadataReaderStorage(String... extensions) { - this.extensions = extensions; - } - - public MetadataReaderStorage ofReaders(IModMetadataReader... readers) { - this.readers = readers; - return this; - } - - public MetadataReaderStorage ofDefaultDesc(String defaultDesc) { - this.defaultDesc = defaultDesc; - return this; - } - - public IModMetadataReader[] getReaders() { - return this.readers; - } - - public String getDefaultDesc() { - return this.defaultDesc; - } - } -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java index 560bb97b8b..d29f43dfcd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java @@ -35,7 +35,7 @@ * @author huangyuhui */ @Immutable -public final class LiteModMetadata implements IModMetadataReader { +public final class LiteModMetadata { private final String name; private final String version; private final String mcversion; @@ -110,7 +110,7 @@ public String getUpdateURI() { return updateURI; } - public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { try (ZipFile zipFile = new ZipFile(modFile.toFile())) { ZipEntry entry = zipFile.getEntry("litemod.json"); if (entry == null) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java index 10bc0c900a..7ad2b28064 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java @@ -38,7 +38,7 @@ import java.util.List; @Immutable -public class PackMcMeta implements Validation, IModMetadataReader { +public class PackMcMeta implements Validation { @SerializedName("pack") private final PackInfo pack; @@ -144,7 +144,7 @@ public PackInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationC } } - public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path mcmod = fs.getPath("pack.mcmeta"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a resource pack."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java index a68f803cb4..d2515d192c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java @@ -17,7 +17,7 @@ import java.util.stream.Collectors; @Immutable -public final class QuiltModMetadata implements IModMetadataReader { +public final class QuiltModMetadata { private static final class QuiltLoader { private static final class Metadata { private final String name; @@ -49,12 +49,12 @@ public QuiltLoader(String id, String version, Metadata metadata) { private final int schema_version; private final QuiltLoader quilt_loader; - public QuiltModMetadata() { - this.schema_version = -1; - this.quilt_loader = null; + public QuiltModMetadata(int schemaVersion, QuiltLoader quiltLoader) { + this.schema_version = schemaVersion; + this.quilt_loader = quiltLoader; } - public LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path path = fs.getPath("quilt.mod.json"); if (Files.notExists(path)) { throw new IOException("File " + modFile + " is not a Quilt mod."); From a49d2f88c3f2258f09c5ee4820f208460e369ac2 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 26 Sep 2023 20:45:50 +0800 Subject: [PATCH 065/104] Log a warning message when 'hmcl.update_source.override' is used. --- HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java | 5 +++++ HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java | 1 + 2 files changed, 6 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java index bed9714f2e..e60a3ef0ea 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java @@ -38,6 +38,7 @@ import org.jackhuang.hmcl.upgrade.resource.RemoteResourceManager; import org.jackhuang.hmcl.util.CrashReporter; import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.JarUtils; import org.jackhuang.hmcl.util.platform.Architecture; @@ -292,6 +293,10 @@ public static void main(String[] args) { if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE")); + if (System.getProperty("hmcl.update_source.override") != null) { + Logging.LOG.log(Level.WARNING, "'hmcl.update_source.override' is deprecated! Please use 'hmcl.hmcl_update_source.override' instead"); + } + launch(Launcher.class, args); } catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter. CRASH_REPORTER.uncaughtException(Thread.currentThread(), e); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 048cbf5063..304ccf6281 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -37,6 +37,7 @@ private Metadata() {} public static final String TITLE = NAME + " " + VERSION; public static final String FULL_TITLE = FULL_NAME + " v" + VERSION; + // hmcl.update_source.override is deprecated. If it is used, a warning message will be printed in org.jackhuang.hmcl.Launcher.main . public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.hmcl_update_source.override", System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link")); public static final String RESOURCE_UPDATE_URL = System.getProperty("hmcl.resource_update_source.override", "https://github.com/huanghongxun/HMCL/raw/javafx/data-json/dynamic-remote-resources.json"); public static final String CONTACT_URL = "https://docs.hmcl.net/help.html"; From 13600e5de5437035e3e18d2b0e81e64892ce24ce Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 3 Oct 2023 00:07:11 +0800 Subject: [PATCH 066/104] Fix chinese searching --- .../game/LocalizedRemoteModRepository.java | 72 +++++++++---------- .../hmcl/ui/versions/ModDownloadListPage.java | 9 +++ .../ui/versions/ModpackDownloadListPage.java | 9 +++ .../ResourcePackDownloadListPage.java | 9 +++ .../jackhuang/hmcl/util/io/NetworkUtils.java | 4 +- 5 files changed, 63 insertions(+), 40 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java index 187d4716f9..747713f7f8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java @@ -34,6 +34,8 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor protected abstract RemoteModRepository getBackedRemoteModRepository(); + protected abstract SortType getBackedRemoteModRepositorySortOrder(); + @Override public SearchResult search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException { if (!StringUtils.containsChinese(searchFilter)) { @@ -56,10 +58,10 @@ public SearchResult search(String gameVersion, Category category, int pageOffset if (count >= 3) break; } - SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), sort, sortOrder); - Set searchFilterLetters = new HashSet<>(); + SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder); + Set searchFilterLetters = new HashSet<>(); for (int i = 0; i < searchFilter.length(); i++) { - searchFilterLetters.add(searchFilter.subSequence(i, i + 1)); + searchFilterLetters.add(searchFilter.charAt(i)); } List chineseSearchResult = new ArrayList<>(); @@ -75,42 +77,36 @@ public SearchResult search(String gameVersion, Category category, int pageOffset int totalPages = searchResult.getTotalPages(); searchResult = null; // Release memory - return new SearchResult(Stream.of(chineseSearchResult, englishSearchResult).flatMap(remoteMods -> { - if (remoteMods == chineseSearchResult) { - return chineseSearchResult.stream().map(remoteMod -> { - ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); - if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { - return Pair.pair(remoteMod, Integer.MAX_VALUE); - } - String chineseRemoteModName = chineseRemoteMod.getName(); - if (searchFilter.length() == 0 || chineseRemoteModName.length() == 0) { - return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length())); - } - int[][] lev = new int[searchFilter.length() + 1][chineseRemoteModName.length() + 1]; - for (int i = 0; i < chineseRemoteModName.length() + 1; i++) { - lev[0][i] = i; - } - for (int i = 0; i < searchFilter.length() + 1; i++) { - lev[i][0] = i; - } - for (int i = 1; i < searchFilter.length() + 1; i++) { - for (int j = 1; j < chineseRemoteModName.length() + 1; j++) { - int countByInsert = lev[i][j - 1] + 1; - int countByDel = lev[i - 1][j] + 1; - int countByReplace = searchFilter.charAt(i - 1) == chineseRemoteModName.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; - lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); - } - } - - return Pair.pair( - remoteMod, - lev[searchFilter.length()][chineseRemoteModName.length()] - (searchFilterLetters.stream().anyMatch(chineseRemoteModName::contains) ? CONTAIN_CHINESE_WEIGHT : 0) - ); - }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey); - } else { - return englishSearchResult.stream(); + return new SearchResult(Stream.concat(chineseSearchResult.stream().map(remoteMod -> { + ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); + if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { + return Pair.pair(remoteMod, Integer.MAX_VALUE); + } + String chineseRemoteModName = chineseRemoteMod.getName(); + if (searchFilter.length() == 0 || chineseRemoteModName.length() == 0) { + return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length())); + } + int[][] lev = new int[searchFilter.length() + 1][chineseRemoteModName.length() + 1]; + for (int i = 0; i < chineseRemoteModName.length() + 1; i++) { + lev[0][i] = i; } - }), totalPages); + for (int i = 0; i < searchFilter.length() + 1; i++) { + lev[i][0] = i; + } + for (int i = 1; i < searchFilter.length() + 1; i++) { + for (int j = 1; j < chineseRemoteModName.length() + 1; j++) { + int countByInsert = lev[i][j - 1] + 1; + int countByDel = lev[i - 1][j] + 1; + int countByReplace = searchFilter.charAt(i - 1) == chineseRemoteModName.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; + lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); + } + } + + return Pair.pair( + remoteMod, + lev[searchFilter.length()][chineseRemoteModName.length()] - (searchFilterLetters.stream().anyMatch(c -> chineseRemoteModName.indexOf(c) >= 0) ? CONTAIN_CHINESE_WEIGHT : 0) + ); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), englishSearchResult.stream()), totalPages); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java index a86291fd87..5deea40292 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java @@ -49,6 +49,15 @@ protected RemoteModRepository getBackedRemoteModRepository() { } } + @Override + protected SortType getBackedRemoteModRepositorySortOrder() { + if ("mods.modrinth".equals(downloadSource.get())) { + return SortType.NAME; + } else { + return SortType.POPULARITY; + } + } + @Override public Type getType() { return Type.MOD; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModpackDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModpackDownloadListPage.java index bcfbec6884..91ee40d05c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModpackDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModpackDownloadListPage.java @@ -49,6 +49,15 @@ protected RemoteModRepository getBackedRemoteModRepository() { } } + @Override + protected SortType getBackedRemoteModRepositorySortOrder() { + if ("mods.modrinth".equals(downloadSource.get())) { + return SortType.NAME; + } else { + return SortType.POPULARITY; + } + } + @Override public Type getType() { return Type.MODPACK; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackDownloadListPage.java index f449fe7e85..05ba07bcc1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackDownloadListPage.java @@ -49,6 +49,15 @@ protected RemoteModRepository getBackedRemoteModRepository() { } } + @Override + protected SortType getBackedRemoteModRepositorySortOrder() { + if ("mods.modrinth".equals(downloadSource.get())) { + return SortType.NAME; + } else { + return SortType.POPULARITY; + } + } + @Override public Type getType() { return Type.MOD; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java index 9db3fbc7c0..09832d69e3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java @@ -144,8 +144,8 @@ public static HttpURLConnection resolveConnection(HttpURLConnection conn) throws while (true) { conn.setUseCaches(false); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); + conn.setConnectTimeout(8000); + conn.setReadTimeout(8000); conn.setInstanceFollowRedirects(false); Map> properties = conn.getRequestProperties(); String method = conn.getRequestMethod(); From f52d64d1ef364d8a1545aafa782245c3649ab48d Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 3 Oct 2023 00:35:42 +0800 Subject: [PATCH 067/104] Enhance chinese searching. --- .../game/LocalizedRemoteModRepository.java | 6 +++--- .../hmcl/mod/RemoteModRepository.java | 21 +++++++++++++++---- .../curse/CurseForgeRemoteModRepository.java | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java index 747713f7f8..f41d2f566d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java @@ -64,9 +64,9 @@ public SearchResult search(String gameVersion, Category category, int pageOffset searchFilterLetters.add(searchFilter.charAt(i)); } - List chineseSearchResult = new ArrayList<>(); - List englishSearchResult = new ArrayList<>(); - searchResult.getResults().forEachOrdered(remoteMod -> { + List chineseSearchResult = new LinkedList<>(); + List englishSearchResult = new LinkedList<>(); + searchResult.getUnsortedResults().forEach(remoteMod -> { ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) { chineseSearchResult.add(remoteMod); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java index a33a374693..c915faf252 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java @@ -52,17 +52,30 @@ enum SortOrder { } class SearchResult { - private final Stream results; + private final Stream sortedResults; + + private final Stream unsortedResults; private final int totalPages; - public SearchResult(Stream results, int pages) { - this.results = results; + public SearchResult(Stream sortedResults, Stream unsortedResults, int totalPages) { + this.sortedResults = sortedResults; + this.unsortedResults = unsortedResults; + this.totalPages = totalPages; + } + + public SearchResult(Stream sortedResults, int pages) { + this.sortedResults = sortedResults; + this.unsortedResults = sortedResults; this.totalPages = pages; } public Stream getResults() { - return this.results; + return this.sortedResults; + } + + public Stream getUnsortedResults() { + return this.unsortedResults; } public int getTotalPages() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 282e942520..e2379b8705 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -150,7 +150,7 @@ public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Cat } return pair(remoteMod, diff); - }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.pagination.totalCount); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), res, response.pagination.totalCount); } @Override From 41e8d75d9e63b88c5f6a671e2d8736d0bf1cac83 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 3 Oct 2023 01:10:19 +0800 Subject: [PATCH 068/104] Enhance memory usage --- .../game/LocalizedRemoteModRepository.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java index f41d2f566d..cfdb927330 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java @@ -32,6 +32,8 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepository { private static final int CONTAIN_CHINESE_WEIGHT = 10; + private static final int INITIAL_CAPACITY = 16; + protected abstract RemoteModRepository getBackedRemoteModRepository(); protected abstract SortType getBackedRemoteModRepositorySortOrder(); @@ -42,7 +44,7 @@ public SearchResult search(String gameVersion, Category category, int pageOffset return getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder); } - Set englishSearchFiltersSet = new LinkedHashSet<>(); + Set englishSearchFiltersSet = new HashSet<>(INITIAL_CAPACITY); int count = 0; for (ModTranslations.Mod mod : ModTranslations.getTranslationsByRepositoryType(getType()).searchMod(searchFilter)) { @@ -64,20 +66,25 @@ public SearchResult search(String gameVersion, Category category, int pageOffset searchFilterLetters.add(searchFilter.charAt(i)); } - List chineseSearchResult = new LinkedList<>(); - List englishSearchResult = new LinkedList<>(); - searchResult.getUnsortedResults().forEach(remoteMod -> { + RemoteMod[] searchResultArray = new RemoteMod[pageSize]; + int chineseIndex = 0, englishIndex = searchResultArray.length - 1; + for (Iterator iterator = searchResult.getUnsortedResults().iterator(); iterator.hasNext(); ) { + if (chineseIndex > englishIndex) { + throw new IOException("There are too many search results!"); + } + + RemoteMod remoteMod = iterator.next(); ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) { - chineseSearchResult.add(remoteMod); + searchResultArray[chineseIndex++] = remoteMod; } else { - englishSearchResult.add(remoteMod); + searchResultArray[englishIndex--] = remoteMod; } - }); + } int totalPages = searchResult.getTotalPages(); searchResult = null; // Release memory - return new SearchResult(Stream.concat(chineseSearchResult.stream().map(remoteMod -> { + return new SearchResult(Stream.concat(Arrays.stream(searchResultArray, 0, chineseIndex).map(remoteMod -> { ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { return Pair.pair(remoteMod, Integer.MAX_VALUE); @@ -106,7 +113,7 @@ public SearchResult search(String gameVersion, Category category, int pageOffset remoteMod, lev[searchFilter.length()][chineseRemoteModName.length()] - (searchFilterLetters.stream().anyMatch(c -> chineseRemoteModName.indexOf(c) >= 0) ? CONTAIN_CHINESE_WEIGHT : 0) ); - }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), englishSearchResult.stream()), totalPages); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), Arrays.stream(searchResultArray, englishIndex + 1, searchResultArray.length)), totalPages); } @Override From 5a5aa09399a2aab307d5f5e7cba0b4fb7405b41e Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 4 Oct 2023 14:02:29 +0800 Subject: [PATCH 069/104] Add basic support for NeoForge. --- .../hmcl/ui/versions/DownloadPage.java | 3 ++ .../hmcl/ui/versions/ModListPageSkin.java | 43 +++++++++++++++--- .../main/resources/assets/img/neoForge.png | Bin 0 -> 1202 bytes .../main/resources/assets/img/neoForge@2x.png | Bin 0 -> 1412 bytes .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + .../org/jackhuang/hmcl/mod/ModLoaderType.java | 23 +++------- .../jackhuang/hmcl/mod/curse/CurseAddon.java | 1 + .../modrinth/ModrinthRemoteModRepository.java | 2 + 10 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 HMCL/src/main/resources/assets/img/neoForge.png create mode 100644 HMCL/src/main/resources/assets/img/neoForge@2x.png diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 551f7f8917..4690cc0248 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -399,6 +399,9 @@ private static final class ModItem extends StackPane { case FORGE: content.getTags().add(i18n("install.installer.forge")); break; + case NEO_FORGED: + content.getTags().add(i18n("install.installer.neo_forge")); + break; case FABRIC: content.getTags().add(i18n("install.installer.fabric")); break; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 836e40988f..bf73be6035 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -329,15 +329,28 @@ class ModInfoDialog extends JFXDialogLayout { RemoteMod remoteMod = remoteModRepository.getModById(versionOptional.get().getModid()); FXUtils.runInFX(() -> { for (ModLoaderType modLoaderType : versionOptional.get().getLoaders()) { + String loaderName; switch (modLoaderType) { - case FABRIC: case FORGE: + loaderName = i18n("install.installer.forge"); + break; + case NEO_FORGED: + loaderName = i18n("install.installer.neo_forge"); + break; + case FABRIC: + loaderName = i18n("install.installer.fabric"); + break; case LITE_LOADER: - case QUILT: { - if (!title.getTags().contains(modLoaderType.getLoaderName())) { - title.getTags().add(modLoaderType.getLoaderName()); - } - } + loaderName = i18n("install.installer.liteloader"); + break; + case QUILT: + loaderName = i18n("install.installer.quilt"); + break; + default: + continue; + } + if (!title.getTags().contains(loaderName)) { + title.getTags().add(loaderName); } } @@ -452,7 +465,23 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { content.setTitle(dataItem.getTitle()); content.getTags().clear(); if (dataItem.getMod() != null) { - content.getTags().add(dataItem.getModInfo().getModLoaderType().getLoaderName()); + switch (dataItem.getModInfo().getModLoaderType()) { + case FORGE: + content.getTags().add(i18n("install.installer.forge")); + break; + case NEO_FORGED: + content.getTags().add(i18n("install.installer.neo_forge")); + break; + case FABRIC: + content.getTags().add(i18n("install.installer.fabric")); + break; + case LITE_LOADER: + content.getTags().add(i18n("install.installer.liteloader")); + break; + case QUILT: + content.getTags().add(i18n("install.installer.quilt")); + break; + } if (I18n.getCurrentLocale().getLocale() == Locale.CHINA) { content.getTags().add(dataItem.getMod().getDisplayName()); } diff --git a/HMCL/src/main/resources/assets/img/neoForge.png b/HMCL/src/main/resources/assets/img/neoForge.png new file mode 100644 index 0000000000000000000000000000000000000000..95c3b5cf69190900f7ad27bd0711121f0d79e2d5 GIT binary patch literal 1202 zcmV;j1Wo&iP)Px#1am@3R0s$N2z&@+hyVZtTuDShR9J=0mrHC^RT#&A=gysWrc6775-<^IVSzy< zZ9=iFjj>(G0?Mmu6G9gqr8Rbg>0pVsY+eMc{PA#P8$xVDG6Kv_AP@++jH04t04F|p zUj>=BK}ng{``y4md9Lql_YrS&GLoN>5UQ20B$Ia<8yn-$mC_mI%xBI0^;;<6(0;@7|D@O(#J&pRN3RcPFzrR;@8@ z%r*iQx`t59b^kv6FG!X^X4*>~24yAF^bt+qPNrrMu!Q?;rdArZRbOCBkZU{2{xTnK z_YuF{2f(`3<_L>DDlU(`G_L6+v?Kt*i|+!k23TY6NX8}{dHXD^8Sr>?6lt!J$DMv) z(0sL}ZWGrKcJ0^*_*_6H!Bb*a`Me0^sV$3T8c)-%3UFayph^|O%eNm_GWyE{H=dKN zc&75=r4OYpWq3n<{g40%_^SgzgU6lDvR~CT06;8qpV(vwfKYcUU>9LH7@yt(U;A+Y z+@(I;Ws3p$y{jGoEvlf!902`|J=Yo+h6q!;4(}0);?vF_snTu@9|TyHN$5Fonvc4B z0qEOk`VyK>LemR^zO=m!fVbM)=xp}`5S(zD!R2fA=o>qSq+x{UsW%C1sIMOa2((pC zJDwhbjSdgJTvyj`y{Z78ZChU@(s)&;QR#A;8i+;io4;Cw=#+I2a08G`36d#0(;}y& z$Z1O4%8zN0ERMA`<0+-`^Udp}bP(Qo`s$DhAk)f0DQHOQ21~pd0vX}Ov@o%#HiJM$ zNLllc&JwWlV_Gu>d@gQy1ArStQ9k`SFfH~Rn=$E&oeF`f?E#%J1+vn zL!lt&2J;i}`~7iX1mO6Vn!?}O`IWqSoW&uDJe(U7QiKY%nb(*9_?DVKfQnhtMq1BYUO89HM=rvD0H77Iz<0;D Q@&Et;07*qoM6N<$g5CQrf&c&j literal 0 HcmV?d00001 diff --git a/HMCL/src/main/resources/assets/img/neoForge@2x.png b/HMCL/src/main/resources/assets/img/neoForge@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7d46d392e44d7b7007041bb213ddc4540925b33f GIT binary patch literal 1412 zcmV-~1$+95P)Px#1am@3R0s$N2z&@+hyVZuE=fc|RCt{2T3c)sRT%zfW_P>W?sna5tu0kc14R=h z7BvwRo89o@CAAdff#O4fG#ZUD1_`Dw$V&r>Au&dyA<}(NE~3~%6g4TkK`LmJG|>kO zkuGR2nl81w)7i_+p78;@nKS3?>`bj5&CK_-^X-4m|IfE`zCD-WUxg*X2L@LE~ zz0%k6__}pTqnI-nE-e4#_5SfneW?J6Lm!`xny-=${_l41e}^Lg>jGHAWwk*(9&bP% zPnDMY@Zf&0x^y*}0uSIF9vNxIxI!TxjYg-f^kZ3o^Iv{;^q0#&MYP;qcQz4mW7j9F zc5Mhsjxv8 z228x?!3feFaQcGPoo+ePrsZl|+aTDAQIJjlL3EtEbdunL4WuLGL~_EEr*Pi;HxZn# z_7?1V?#d{nuZ&`x><)}(=cFMm#{(pyvF{Q98WThS5uSs++CaCQAPK;A0 z$irNk05s5UmIm(wqyV9&79H~*povQuBTAXJJSSkk7`o|;2>@pZI0D#SfRYFB012d< zGsx~hrzqhjSR<2~4ICRJxGqXEn&FuREyu21FJspQnFmqA>yzDqQ6!NjxbV3oz+bMI z&<4hLZ)(n`B}{;jOrE90j#C79f}l(r9u2e{qyQX)PAYvGV24nWhjRv$fjp8PMXK}=5E4AFnWV32inSymP20$ha_Uz zoXLsnkM(XI&~gIL4Q_e&qagr|0J*8_YdGL~w1H|^0e=`ThDTY*X7H(RRuU<)N@SSCoBPPCEDL z(ag_3tX)j9AJ2cWm}UR~Fp;Xfk`z{~z9_UmxmC-lE|&<0$?dcS7z)tc(J=!6*qhcz zUh2t}31huB4SVwjTnH0IUmO4a+k@*CSiD79)j<@4KDYw&5#K;r7Lqr2_}Q z3fo_Ov{INLdvfqb%wpI75Z-_4p>Y6MzC_(6pE>o7{wR1!VEZB92*A1k*7y^FE3H!r SKl*(D0000 toVersion() { loaders.stream().flatMap(loader -> { if ("fabric".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FABRIC); else if ("forge".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FORGE); + else if ("neoforge".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.NEO_FORGED); else if ("quilt".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.QUILT); + else if ("liteloader".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.LITE_LOADER); else return Stream.empty(); }).collect(Collectors.toList()) )); From c259a9a5b1f6b0abf334383a3430c46c489f08b4 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 4 Oct 2023 17:04:00 +0800 Subject: [PATCH 070/104] Enable HMCl to show the version list of NeoForged. --- .../hmcl/setting/VersionIconType.java | 1 + .../org/jackhuang/hmcl/ui/InstallerItem.java | 30 +++- .../hmcl/ui/download/InstallersPage.java | 5 +- .../hmcl/ui/download/VersionsPage.java | 3 + .../hmcl/ui/versions/DownloadPage.java | 2 +- .../hmcl/ui/versions/InstallerListPage.java | 4 +- .../hmcl/ui/versions/ModListPageSkin.java | 4 +- .../img/{neoForge.png => neoforged.png} | Bin .../img/{neoForge@2x.png => neoforged@2x.png} | Bin .../resources/assets/lang/I18N.properties | 2 +- .../resources/assets/lang/I18N_zh.properties | 2 +- .../assets/lang/I18N_zh_CN.properties | 2 +- .../download/BMCLAPIDownloadProvider.java | 6 + .../download/DefaultDependencyManager.java | 6 + .../hmcl/download/LibraryAnalyzer.java | 14 +- .../hmcl/download/MojangDownloadProvider.java | 7 +- .../neoforge/NeoForgedBMCLVersionList.java | 134 ++++++++++++++++++ .../neoforge/NeoForgedInstallTask.java | 49 +++++++ .../neoforge/NeoForgedRemoteVersion.java | 21 +++ 19 files changed, 273 insertions(+), 19 deletions(-) rename HMCL/src/main/resources/assets/img/{neoForge.png => neoforged.png} (100%) rename HMCL/src/main/resources/assets/img/{neoForge@2x.png => neoforged@2x.png} (100%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java index d641472f1a..85afe151b0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java @@ -27,6 +27,7 @@ public enum VersionIconType { CRAFT_TABLE("/assets/img/craft_table.png"), FABRIC("/assets/img/fabric.png"), FORGE("/assets/img/forge.png"), + NEO_FORGED("/assets/img/neoforged.png"), FURNACE("/assets/img/furnace.png"), QUILT("/assets/img/quilt.png"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index c94b5c085e..8036ba0003 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -92,6 +92,9 @@ public InstallerItem(String id) { case "quilt-api": imageUrl = "/assets/img/quilt.png"; break; + case "neoforged": + imageUrl = "/assets/img/neoforged.png"; + break; default: imageUrl = null; break; @@ -122,6 +125,7 @@ public static class InstallerItemGroup { public final InstallerItem fabric = new InstallerItem(FABRIC); public final InstallerItem fabricApi = new InstallerItem(FABRIC_API); public final InstallerItem forge = new InstallerItem(FORGE); + public final InstallerItem neoForged = new InstallerItem(NEO_FORGED); public final InstallerItem liteLoader = new InstallerItem(LITELOADER); public final InstallerItem optiFine = new InstallerItem(OPTIFINE); public final InstallerItem quilt = new InstallerItem(QUILT); @@ -131,30 +135,41 @@ public InstallerItemGroup() { forge.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); + if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); + return null; + }, fabric.libraryVersion, quilt.libraryVersion, neoForged.libraryVersion)); + + neoForged.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { + if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); + if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); + if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, forge.libraryVersion)); liteLoader.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); + if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, neoForged.libraryVersion)); optiFine.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); + if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, neoForged.libraryVersion)); for (InstallerItem fabric : new InstallerItem[]{fabric, fabricApi}) { fabric.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); + if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); if (liteLoader.libraryVersion.get() != null) return LITELOADER.getPatchId(); if (optiFine.libraryVersion.get() != null) return OPTIFINE.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); if (quiltApi.libraryVersion.get() != null) return QUILT_API.getPatchId(); return null; - }, forge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion, quilt.libraryVersion, quiltApi.libraryVersion)); + }, forge.libraryVersion, neoForged.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion, quilt.libraryVersion, quiltApi.libraryVersion)); } fabricApi.dependencyName.bind(Bindings.createStringBinding(() -> { @@ -167,10 +182,11 @@ public InstallerItemGroup() { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (fabricApi.libraryVersion.get() != null) return FABRIC_API.getPatchId(); if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); + if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); if (liteLoader.libraryVersion.get() != null) return LITELOADER.getPatchId(); if (optiFine.libraryVersion.get() != null) return OPTIFINE.getPatchId(); return null; - }, fabric.libraryVersion, fabricApi.libraryVersion, forge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion)); + }, fabric.libraryVersion, fabricApi.libraryVersion, forge.libraryVersion, neoForged.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion)); } quiltApi.dependencyName.bind(Bindings.createStringBinding(() -> { @@ -180,7 +196,7 @@ public InstallerItemGroup() { } public InstallerItem[] getLibraries() { - return new InstallerItem[]{game, forge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; + return new InstallerItem[]{game, forge, neoForged, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; } } @@ -206,7 +222,7 @@ public static class InstallerItemSkin extends SkinBase { pane.pseudoClassStateChanged(CARD, control.style == Style.CARD); if (control.imageUrl != null) { - ImageView view = new ImageView(FXUtils.newRemoteImage(control.imageUrl)); + ImageView view = new ImageView(FXUtils.newBuiltinImage(control.imageUrl)); Node node = FXUtils.limitingSize(view, 32, 32); node.setMouseTransparent(true); node.getStyleClass().add("installer-item-image"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index 19235aa737..4922540e6a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -31,6 +31,7 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.ui.Controllers; @@ -72,9 +73,9 @@ public InstallersPage(WizardController controller, HMCLGameRepository repository for (InstallerItem library : group.getLibraries()) { String libraryId = library.getLibraryId(); - if (libraryId.equals("game")) continue; + if (libraryId.equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) continue; library.action.set(e -> { - if ("fabric-api".equals(libraryId)) { + if (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)) { Controllers.dialog(i18n("install.installer.fabric-api.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index 9bee8d246b..b6fd3825db 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -36,6 +36,7 @@ import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion; import org.jackhuang.hmcl.download.game.GameRemoteVersion; import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion; +import org.jackhuang.hmcl.download.neoforge.NeoForgedRemoteVersion; import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion; @@ -335,6 +336,8 @@ else if (remoteVersion instanceof OptiFineRemoteVersion) iconType = VersionIconType.COMMAND; else if (remoteVersion instanceof ForgeRemoteVersion) iconType = VersionIconType.FORGE; + else if (remoteVersion instanceof NeoForgedRemoteVersion) + iconType = VersionIconType.NEO_FORGED; else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion) iconType = VersionIconType.FABRIC; else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof QuiltAPIRemoteVersion) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 4690cc0248..b9f097edd7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -400,7 +400,7 @@ private static final class ModItem extends StackPane { content.getTags().add(i18n("install.installer.forge")); break; case NEO_FORGED: - content.getTags().add(i18n("install.installer.neo_forge")); + content.getTags().add(i18n("install.installer.neoforged")); break; case FABRIC: content.getTags().add(i18n("install.installer.fabric")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index f3e66e86f6..8428dbfad4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -86,7 +86,7 @@ public void loadVersion(Profile profile, String versionId) { InstallerItem.InstallerItemGroup group = new InstallerItem.InstallerItemGroup(); - // Conventional libraries: game, fabric, forge, liteloader, optifine + // Conventional libraries: game, fabric, forge, neoforged, liteloader, optifine for (InstallerItem installerItem : group.getLibraries()) { String libraryId = installerItem.getLibraryId(); String libraryVersion = analyzer.getVersion(libraryId).orElse(null); @@ -96,7 +96,7 @@ public void loadVersion(Profile profile, String versionId) { installerItem.action.set(e -> { Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion)); }); - boolean removable = !"game".equals(libraryId) && libraryVersion != null; + boolean removable = !LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(libraryId) && libraryVersion != null; installerItem.removable.set(removable); if (removable) { Runnable action = removeAction.apply(libraryId); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index bf73be6035..c8010a14a8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -335,7 +335,7 @@ class ModInfoDialog extends JFXDialogLayout { loaderName = i18n("install.installer.forge"); break; case NEO_FORGED: - loaderName = i18n("install.installer.neo_forge"); + loaderName = i18n("install.installer.neoforged"); break; case FABRIC: loaderName = i18n("install.installer.fabric"); @@ -470,7 +470,7 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { content.getTags().add(i18n("install.installer.forge")); break; case NEO_FORGED: - content.getTags().add(i18n("install.installer.neo_forge")); + content.getTags().add(i18n("install.installer.neoforged")); break; case FABRIC: content.getTags().add(i18n("install.installer.fabric")); diff --git a/HMCL/src/main/resources/assets/img/neoForge.png b/HMCL/src/main/resources/assets/img/neoforged.png similarity index 100% rename from HMCL/src/main/resources/assets/img/neoForge.png rename to HMCL/src/main/resources/assets/img/neoforged.png diff --git a/HMCL/src/main/resources/assets/img/neoForge@2x.png b/HMCL/src/main/resources/assets/img/neoforged@2x.png similarity index 100% rename from HMCL/src/main/resources/assets/img/neoForge@2x.png rename to HMCL/src/main/resources/assets/img/neoforged@2x.png diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 2940183f25..258433d4e1 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -618,7 +618,7 @@ install.installer.fabric=Fabric install.installer.fabric-api=Fabric API install.installer.fabric-api.warning=Warning: Fabric API is a mod, and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not work. If you do want to change these settings, you should reinstall it. install.installer.forge=Forge -install.installer.neo_forge=NeoForge +install.installer.neoforged=NeoForge install.installer.game=Minecraft install.installer.incompatible=Incompatible with %s install.installer.install=Install %s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 4b063f01ba..2c82fc2502 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -496,7 +496,7 @@ install.installer.fabric=Fabric install.installer.fabric-api=Fabric API install.installer.fabric-api.warning=警告:Fabric API 是 Mod,將會被安裝到新遊戲的 Mod 資料夾,請你在安裝遊戲後不要修改當前遊戲的版本隔離/遊戲運行路徑設置,如果你在之後修改了相關設置,Fabric API 需要被重新安裝。 install.installer.forge=Forge -install.installer.neo_forge=NeoForge +install.installer.neoforged=NeoForge install.installer.game=Minecraft install.installer.incompatible=與 %s 不相容 install.installer.install=安裝%s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index fe1e696979..4d6b49a34b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -495,7 +495,7 @@ install.installer.fabric=Fabric install.installer.fabric-api=Fabric API install.installer.fabric-api.warning=警告:Fabric API 是一个 Mod,将会被安装到新游戏的 Mod 文件夹,请你在安装游戏后不要修改当前游戏的版本隔离/游戏运行路径设置,如果你在之后修改了相关设置,Fabric API 需要被重新安装。 install.installer.forge=Forge -install.installer.neo_forge=NeoForge +install.installer.neoforged=NeoForge install.installer.game=Minecraft install.installer.incompatible=与 %s 不兼容 install.installer.install=安装 %s diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java index 69aeebda7b..cfcc3b3514 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -22,6 +22,7 @@ import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList; +import org.jackhuang.hmcl.download.neoforge.NeoForgedBMCLVersionList; import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; import org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList; import org.jackhuang.hmcl.download.quilt.QuiltVersionList; @@ -36,6 +37,7 @@ public class BMCLAPIDownloadProvider implements DownloadProvider { private final FabricVersionList fabric; private final FabricAPIVersionList fabricApi; private final ForgeBMCLVersionList forge; + private final NeoForgedBMCLVersionList neoforged; private final LiteLoaderBMCLVersionList liteLoader; private final OptiFineBMCLVersionList optifine; private final QuiltVersionList quilt; @@ -47,6 +49,7 @@ public BMCLAPIDownloadProvider(String apiRoot) { this.fabric = new FabricVersionList(this); this.fabricApi = new FabricAPIVersionList(this); this.forge = new ForgeBMCLVersionList(apiRoot); + this.neoforged = new NeoForgedBMCLVersionList(apiRoot); this.liteLoader = new LiteLoaderBMCLVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); this.quilt = new QuiltVersionList(this); @@ -78,6 +81,8 @@ public VersionList getVersionListById(String id) { return fabricApi; case "forge": return forge; + case "neoforged": + return neoforged; case "liteloader": return liteLoader; case "optifine": @@ -102,6 +107,7 @@ public String injectURL(String baseURL) { .replace("https://libraries.minecraft.net", apiRoot + "/libraries") .replaceFirst("https?://files\\.minecraftforge\\.net/maven", apiRoot + "/maven") .replace("https://maven.minecraftforge.net", apiRoot + "/maven") + .replace("https://maven.neoforged.net/releases/net/neoforged/forge", apiRoot + "/maven/net/neoforged/forge") .replace("http://dl.liteloader.com/versions/versions.json", apiRoot + "/maven/com/mumfrey/liteloader/versions.json") .replace("http://dl.liteloader.com/versions", apiRoot + "/maven") .replace("https://meta.fabricmc.net", apiRoot + "/fabric-meta") diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java index a492c66c26..5770848440 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -21,6 +21,7 @@ import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.download.neoforge.NeoForgedInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.game.Artifact; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -179,6 +180,11 @@ public Task installLibraryAsync(Version oldVersion, Path installer) { return Task .composeAsync(() -> { + try { + return NeoForgedInstallTask.install(this, oldVersion, installer); + } catch (IOException ignore) { + } + try { return ForgeInstallTask.install(this, oldVersion, installer); } catch (IOException ignore) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index e1d78f0f2e..d0ba6ae760 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -113,7 +113,7 @@ private Version removingMatchedLibrary(Version version, String libraryId) { /** * Remove library by library id - * @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"/"quilt" + * @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"/"quilt"/"neoforged" * @return this */ public LibraryAnalyzer removeLibrary(String libraryId) { @@ -187,6 +187,18 @@ public String patchVersion(String libraryVersion) { return super.patchVersion(libraryVersion); } }, + NEO_FORGED(true, "neoforged", Pattern.compile("net\\.minecraftforge"), Pattern.compile("(forge|fmlloader)"), ModLoaderType.NEO_FORGED) { + private final Pattern NEO_FORGE_VERSION_MATCHER = Pattern.compile("^([0-9.]+)-(?[0-9.]+)(-([0-9.]+))?$"); + + @Override + public String patchVersion(String libraryVersion) { + Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion); + if (matcher.find()) { + return matcher.group("neoforged"); + } + return super.patchVersion(libraryVersion); + } + }, LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader"), ModLoaderType.LITE_LOADER), OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$"), null), QUILT(true, "quilt", Pattern.compile("org\\.quiltmc"), Pattern.compile("quilt-loader"), ModLoaderType.QUILT), diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java index 34736b4c8d..091210409b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -22,19 +22,21 @@ import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList; +import org.jackhuang.hmcl.download.neoforge.NeoForgedBMCLVersionList; import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; import org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList; import org.jackhuang.hmcl.download.quilt.QuiltVersionList; /** - * @see http://wiki.vg * @author huangyuhui + * @see http://wiki.vg */ public class MojangDownloadProvider implements DownloadProvider { private final GameVersionList game; private final FabricVersionList fabric; private final FabricAPIVersionList fabricApi; private final ForgeBMCLVersionList forge; + private final NeoForgedBMCLVersionList neoforged; private final LiteLoaderVersionList liteLoader; private final OptiFineBMCLVersionList optifine; private final QuiltVersionList quilt; @@ -47,6 +49,7 @@ public MojangDownloadProvider() { this.fabric = new FabricVersionList(this); this.fabricApi = new FabricAPIVersionList(this); this.forge = new ForgeBMCLVersionList(apiRoot); + this.neoforged = new NeoForgedBMCLVersionList(apiRoot); this.liteLoader = new LiteLoaderVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); this.quilt = new QuiltVersionList(this); @@ -74,6 +77,8 @@ public VersionList getVersionListById(String id) { return fabricApi; case "forge": return forge; + case "neoforged": + return neoforged; case "liteloader": return liteLoader; case "optifine": diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java new file mode 100644 index 0000000000..2ab0e7c7bd --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java @@ -0,0 +1,134 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.neoforge; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.gson.Validation; +import org.jackhuang.hmcl.util.io.HttpRequest; + +import java.time.Instant; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import static org.jackhuang.hmcl.util.Lang.wrap; + +public final class NeoForgedBMCLVersionList extends VersionList { + private final String apiRoot; + + /** + * @param apiRoot API Root of BMCLAPI implementations + */ + public NeoForgedBMCLVersionList(String apiRoot) { + this.apiRoot = apiRoot; + } + + @Override + public boolean hasType() { + return false; + } + + @Override + public CompletableFuture loadAsync() { + throw new UnsupportedOperationException("NeoForgedBMCLVersionList does not support loading the entire Forge remote version list."); + } + + @Override + public CompletableFuture refreshAsync() { + throw new UnsupportedOperationException("NeoForgedBMCLVersionList does not support loading the entire Forge remote version list."); + } + + @Override + public CompletableFuture refreshAsync(String gameVersion) { + return CompletableFuture.completedFuture((Void) null) + .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).>getJson(new TypeToken>(){}.getType()))) + .thenAcceptAsync(neoForgedVersions -> { + lock.writeLock().lock(); + + try { + versions.clear(gameVersion); + for (NeoForgedVersion neoForgedVersion : neoForgedVersions) { + versions.put(gameVersion, new NeoForgedRemoteVersion( + neoForgedVersion.mcVersion, + neoForgedVersion.version, + Date.from(Instant.now()), + Lang.immutableListOf( + apiRoot + "/neoforge/version/" + neoForgedVersion.version + "/download/installer.jar" + ) + )); + } + } finally { + lock.writeLock().unlock(); + } + }); + } + + @Override + public Optional getVersion(String gameVersion, String remoteVersion) { + remoteVersion = StringUtils.substringAfter(remoteVersion, "-", remoteVersion); + return super.getVersion(gameVersion, remoteVersion); + } + + @Immutable + private static final class NeoForgedVersion implements Validation { + private final String rawVersion; + + private final String version; + + @SerializedName("mcversion") + private final String mcVersion; + + public NeoForgedVersion(String rawVersion, String version, String mcVersion) { + this.rawVersion = rawVersion; + this.version = version; + this.mcVersion = mcVersion; + } + + public String getRawVersion() { + return this.rawVersion; + } + + public String getVersion() { + return this.version; + } + + public String getMcVersion() { + return this.mcVersion; + } + + @Override + public void validate() throws JsonParseException { + if (this.rawVersion == null) { + throw new JsonParseException("NeoForgedVersion rawVersion cannot be null."); + } + if (this.version == null) { + throw new JsonParseException("NeoForgedVersion version cannot be null."); + } + if (this.mcVersion == null) { + throw new JsonParseException("NeoForgedVersion mcversion cannot be null."); + } + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java new file mode 100644 index 0000000000..d3f1bc5fca --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java @@ -0,0 +1,49 @@ +package org.jackhuang.hmcl.download.neoforge; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.VersionMismatchException; +import org.jackhuang.hmcl.download.forge.ForgeInstallTask; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.CompressingUtils; +import org.jackhuang.hmcl.util.io.FileUtils; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class NeoForgedInstallTask { + private NeoForgedInstallTask() { + } + + public static Task install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { + Optional gameVersion = dependencyManager.getGameRepository().getGameVersion(version); + if (!gameVersion.isPresent()) throw new IOException(); + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + String installProfileText = FileUtils.readText(fs.getPath("install_profile.json")); + Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); + if (!installProfileText.toLowerCase(Locale.ROOT).contains("neoforge")) { + throw new IOException(); + } + Object p = installProfile.get("profile"); + if (!(p instanceof String)) { + throw new IOException(); + } + if (!p.equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId())) { + throw new IOException(); + } + } + + return ForgeInstallTask.install(dependencyManager, version, installer).thenApplyAsync(neoForgeVersion -> { + if (!neoForgeVersion.getId().equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) || neoForgeVersion.getVersion() == null) { + throw new IOException("Invalid neoforged version null."); + } + return neoForgeVersion.setId(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId()).setVersion(neoForgeVersion.getVersion().replace(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId())); + }); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java new file mode 100644 index 0000000000..075867eb30 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java @@ -0,0 +1,21 @@ +package org.jackhuang.hmcl.download.neoforge; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; + +import java.util.Date; +import java.util.List; + +public class NeoForgedRemoteVersion extends RemoteVersion { + public NeoForgedRemoteVersion(String gameVersion, String selfVersion, Date releaseDate, List urls) { + super(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId(), gameVersion, selfVersion, releaseDate, urls); + } + + @Override + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + throw new UnsupportedOperationException("Cannot install NeoForged automatically."); + } +} From 29ccb4caeb016d2806947c7f3a7d6fc28b89e6f4 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 4 Oct 2023 17:11:15 +0800 Subject: [PATCH 071/104] Fix checkstyle --- .../jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java index d3f1bc5fca..2c73c65e0f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java @@ -17,7 +17,7 @@ import java.util.Map; import java.util.Optional; -public class NeoForgedInstallTask { +public final class NeoForgedInstallTask { private NeoForgedInstallTask() { } From f8a01a7182e39fcb567a0b1b58240b703923ac7d Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 4 Oct 2023 17:38:46 +0800 Subject: [PATCH 072/104] Enable HMCL to download and install NeoForged from BMCL. Fix some bugs. --- .../hmcl/ui/construct/TaskListPane.java | 1 + .../neoforge/NeoForgedBMCLVersionList.java | 3 - .../neoforge/NeoForgedInstallTask.java | 69 +++++++++++++++++-- .../neoforge/NeoForgedRemoteVersion.java | 7 +- 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index a320a6b587..b63067d6ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -248,6 +248,7 @@ public StageNode(String stage) { case "hmcl.install.assets": message = i18n("assets.download"); break; case "hmcl.install.game": message = i18n("install.installer.install", i18n("install.installer.game") + " " + stageValue); break; case "hmcl.install.forge": message = i18n("install.installer.install", i18n("install.installer.forge") + " " + stageValue); break; + case "hmcl.install.neoforged": message = i18n("install.installer.install", i18n("install.installer.neoforged") + " " + stageValue); break; case "hmcl.install.liteloader": message = i18n("install.installer.install", i18n("install.installer.liteloader") + " " + stageValue); break; case "hmcl.install.optifine": message = i18n("install.installer.install", i18n("install.installer.optifine") + " " + stageValue); break; case "hmcl.install.fabric": message = i18n("install.installer.install", i18n("install.installer.fabric") + " " + stageValue); break; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java index 2ab0e7c7bd..3fde0e6f8a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java @@ -27,8 +27,6 @@ import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.HttpRequest; -import java.time.Instant; -import java.util.Date; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -73,7 +71,6 @@ public CompletableFuture refreshAsync(String gameVersion) { versions.put(gameVersion, new NeoForgedRemoteVersion( neoForgedVersion.mcVersion, neoForgedVersion.version, - Date.from(Instant.now()), Lang.immutableListOf( apiRoot + "/neoforge/version/" + neoForgedVersion.version + "/download/installer.jar" ) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java index 2c73c65e0f..3a48cdcfab 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java @@ -5,6 +5,7 @@ import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.forge.ForgeInstallTask; import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; @@ -12,13 +13,71 @@ import java.io.IOException; import java.nio.file.FileSystem; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; +import java.util.*; -public final class NeoForgedInstallTask { - private NeoForgedInstallTask() { +public final class NeoForgedInstallTask extends Task { + private final DefaultDependencyManager dependencyManager; + + private final Version version; + + private final NeoForgedRemoteVersion remoteVersion; + + private Path installer = null; + + private FileDownloadTask dependent; + + private Task dependency; + + public NeoForgedInstallTask(DefaultDependencyManager dependencyManager, Version version, NeoForgedRemoteVersion remoteVersion) { + this.dependencyManager = dependencyManager; + this.version = version; + this.remoteVersion = remoteVersion; + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + installer = Files.createTempFile("neoforged-installer", ".jar"); + + dependent = new FileDownloadTask( + dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()), + installer.toFile(), null + ); + dependent.setCacheRepository(dependencyManager.getCacheRepository()); + dependent.setCaching(true); + dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + } + + @Override + public boolean doPostExecute() { + return true; + } + + @Override + public void postExecute() throws Exception { + Files.deleteIfExists(installer); + this.setResult(dependency.getResult()); + } + + @Override + public Collection> getDependents() { + return Collections.singleton(dependent); + } + + @Override + public Collection> getDependencies() { + return Collections.singleton(dependency); + } + + @Override + public void execute() throws Exception { + dependency = install(dependencyManager, version, installer); } public static Task install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java index 075867eb30..702f3fa2bb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java @@ -6,16 +6,15 @@ import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.Task; -import java.util.Date; import java.util.List; public class NeoForgedRemoteVersion extends RemoteVersion { - public NeoForgedRemoteVersion(String gameVersion, String selfVersion, Date releaseDate, List urls) { - super(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId(), gameVersion, selfVersion, releaseDate, urls); + public NeoForgedRemoteVersion(String gameVersion, String selfVersion, List urls) { + super(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId(), gameVersion, selfVersion, null, urls); } @Override public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { - throw new UnsupportedOperationException("Cannot install NeoForged automatically."); + return new NeoForgedInstallTask(dependencyManager, baseVersion, this); } } From 2a99e1d006342e6e7bbfb3f7cc41b9a92b2697bc Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 4 Oct 2023 18:10:25 +0800 Subject: [PATCH 073/104] Close the mod version dialog window after clicking the downloading / save as button if the dependency list is empty. --- .../jackhuang/hmcl/ui/versions/DownloadPage.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 551f7f8917..d968920b9e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -452,11 +452,21 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { JFXButton downloadButton = new JFXButton(i18n("download")); downloadButton.getStyleClass().add("dialog-accept"); - downloadButton.setOnAction(e -> selfPage.download(version)); + downloadButton.setOnAction(e -> { + if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) { + fireEvent(new DialogCloseEvent()); + } + selfPage.download(version); + }); JFXButton saveAsButton = new JFXButton(i18n("button.save_as")); saveAsButton.getStyleClass().add("dialog-accept"); - saveAsButton.setOnAction(e -> selfPage.saveAs(version)); + saveAsButton.setOnAction(e -> { + if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) { + fireEvent(new DialogCloseEvent()); + } + selfPage.saveAs(version); + }); JFXButton cancelButton = new JFXButton(i18n("button.cancel")); cancelButton.getStyleClass().add("dialog-cancel"); From b08bdb329427fb538fde6644b19f4ac020475616 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 4 Oct 2023 18:23:39 +0800 Subject: [PATCH 074/104] Fix --- .../java/org/jackhuang/hmcl/ui/construct/TaskListPane.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index b63067d6ca..fe0f09de1a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -36,6 +36,7 @@ import org.jackhuang.hmcl.download.game.GameInstallTask; import org.jackhuang.hmcl.download.java.JavaDownloadTask; import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask; +import org.jackhuang.hmcl.download.neoforge.NeoForgedInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.quilt.QuiltAPIInstallTask; import org.jackhuang.hmcl.download.quilt.QuiltInstallTask; @@ -123,6 +124,8 @@ public void onRunning(Task task) { task.setName(i18n("install.installer.install", i18n("install.installer.game"))); } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.forge"))); + } else if (task instanceof NeoForgedInstallTask) { + task.setName(i18n("install.installer.install", i18n("install.installer.neoforged"))); } else if (task instanceof LiteLoaderInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.liteloader"))); } else if (task instanceof OptiFineInstallTask) { From f6f855b92379de7052693a246b3cfcead5509af6 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 4 Oct 2023 21:37:05 +0800 Subject: [PATCH 075/104] Enable HMCL to install NeoForge from modpacks. --- .../org/jackhuang/hmcl/mod/curse/CurseInstallTask.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java index a195a11206..2042187bf6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java @@ -57,9 +57,9 @@ public final class CurseInstallTask extends Task { * Constructor. * * @param dependencyManager the dependency manager. - * @param zipFile the CurseForge modpack file. - * @param manifest The manifest content of given CurseForge modpack. - * @param name the new version name + * @param zipFile the CurseForge modpack file. + * @param manifest The manifest content of given CurseForge modpack. + * @param name the new version name * @see CurseManifest#readCurseForgeModpackManifest */ public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, CurseManifest manifest, String name) { @@ -81,6 +81,8 @@ public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile builder.version("forge", modLoader.getId().substring("forge-".length())); } else if (modLoader.getId().startsWith("fabric-")) { builder.version("fabric", modLoader.getId().substring("fabric-".length())); + } else if (modLoader.getId().startsWith("neoforge-")) { + builder.version("neoforged", modLoader.getId().substring("neoforge-".length())); } } dependents.add(builder.buildAsync()); From 712fb1b5cabde585a02d5d160b7edc06de73da3f Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 6 Oct 2023 01:52:42 +0800 Subject: [PATCH 076/104] Fix the dirty implememtation --- .../hmcl/download/neoforge/NeoForgedInstallTask.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java index 3a48cdcfab..ece6a3edef 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java @@ -84,23 +84,19 @@ public static Task install(DefaultDependencyManager dependencyManager, Optional gameVersion = dependencyManager.getGameRepository().getGameVersion(version); if (!gameVersion.isPresent()) throw new IOException(); try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - String installProfileText = FileUtils.readText(fs.getPath("install_profile.json")); - Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); - if (!installProfileText.toLowerCase(Locale.ROOT).contains("neoforge")) { - throw new IOException(); - } + Map installProfile = JsonUtils.fromNonNullJson(FileUtils.readText(fs.getPath("install_profile.json")), Map.class); Object p = installProfile.get("profile"); - if (!(p instanceof String)) { + if (!LibraryAnalyzer.LibraryType.FORGE.getPatchId().equals(p)) { throw new IOException(); } - if (!p.equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId())) { + if (!Files.exists(fs.getPath("META-INF/NEOFORGE.RSA"))) { throw new IOException(); } } return ForgeInstallTask.install(dependencyManager, version, installer).thenApplyAsync(neoForgeVersion -> { if (!neoForgeVersion.getId().equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) || neoForgeVersion.getVersion() == null) { - throw new IOException("Invalid neoforged version null."); + throw new IOException("Invalid neoforged version."); } return neoForgeVersion.setId(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId()).setVersion(neoForgeVersion.getVersion().replace(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId())); }); From 18436029852c713ad31e7ad3e7322bae69115b38 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 6 Oct 2023 03:50:23 +0800 Subject: [PATCH 077/104] Fix UI --- .../jackhuang/hmcl/ui/download/InstallersPage.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index 4922540e6a..a36a2c09d0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -23,10 +23,7 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.control.Control; -import javafx.scene.control.Label; -import javafx.scene.control.Skin; -import javafx.scene.control.SkinBase; +import javafx.scene.control.*; import javafx.scene.layout.BorderPane; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; @@ -156,10 +153,13 @@ protected InstallersPageSkin(InstallersPage control) { { FlowPane libraryPane = new FlowPane(control.group.getLibraries()); - BorderPane.setMargin(libraryPane, new Insets(16, 0, 16, 0)); libraryPane.setVgap(16); libraryPane.setHgap(16); - root.setCenter(libraryPane); + ScrollPane scrollPane = new ScrollPane(libraryPane); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0)); + root.setCenter(scrollPane); } From 8eb7ef64904da550c11ecc98f719e6d606a718a7 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Wed, 25 Oct 2023 22:10:51 +0800 Subject: [PATCH 078/104] Cache builtin images. --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index b2f7c3a957..c8af8a8a19 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -99,18 +99,22 @@ private FXUtils() { public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace"; - private static final Map imageCache = new ConcurrentHashMap<>(); + private static final Map builtinImageCache = new ConcurrentHashMap<>(); - public static synchronized void shutdown() { - for (Path path : imageCache.values()) { + private static final Map remoteImageCache = new ConcurrentHashMap<>(); + + public static void shutdown() { + for (String url : remoteImageCache.keySet()) { + Path path = remoteImageCache.get(url); try { Files.deleteIfExists(path); } catch (IOException e) { LOG.log(Level.WARNING, String.format("Failed to delete cache file %s.", path), e); } + remoteImageCache.remove(url); } - imageCache.clear(); + builtinImageCache.clear(); } public static void runInFX(Runnable runnable) { @@ -702,11 +706,13 @@ public static Image newBuiltinImage(String url) { * @see ResourceNotFoundError */ public static Image newBuiltinImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) { - try { - return new Image(url, requestedWidth, requestedHeight, preserveRatio, smooth); - } catch (IllegalArgumentException e) { - throw new ResourceNotFoundError("Cannot access image: " + url, e); - } + return builtinImageCache.computeIfAbsent(url, s -> { + try { + return new Image(s, requestedWidth, requestedHeight, preserveRatio, smooth); + } catch (IllegalArgumentException e) { + throw new ResourceNotFoundError("Cannot access image: " + s, e); + } + }); } /** @@ -740,32 +746,33 @@ public static Image newRemoteImage(String url) { * @see ResourceNotFoundError */ public static Image newRemoteImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth, boolean backgroundLoading) { - if (imageCache.containsKey(url)) { - Path path = imageCache.get(url); - if (Files.isReadable(path)) { + Path currentPath = remoteImageCache.get(url); + if (currentPath != null) { + if (Files.isReadable(currentPath)) { try { - return new Image(Files.newInputStream(path), requestedWidth, requestedHeight, preserveRatio, smooth); + return new Image(Files.newInputStream(currentPath), requestedWidth, requestedHeight, preserveRatio, smooth); } catch (IOException e) { LOG.log(Level.WARNING, "An exception encountered while reading data from cached image file.", e); } } + // The file is unavailable or unreadable + remoteImageCache.remove(url); + try { - Files.deleteIfExists(path); + Files.deleteIfExists(currentPath); } catch (IOException e) { LOG.log(Level.WARNING, "An exception encountered while deleting broken cached image file.", e); } - - imageCache.remove(url); } Image image = new Image(url, requestedWidth, requestedHeight, preserveRatio, smooth, backgroundLoading); image.progressProperty().addListener((observable, oldValue, newValue) -> { if (newValue.doubleValue() >= 1.0 && !image.isError() && image.getPixelReader() != null && image.getWidth() > 0.0 && image.getHeight() > 0.0) { Task.runAsync(() -> { - Path path = Files.createTempFile("hmcl-net-resource-cache-", ".cache"); - PNGJavaFXUtils.writeImage(image, path); - imageCache.put(url, path); + Path newPath = Files.createTempFile("hmcl-net-resource-cache-", ".cache"); + PNGJavaFXUtils.writeImage(image, newPath); + remoteImageCache.put(url, newPath); }).start(); } }); From 0a49eb2c1204e4be7dc0df3084faa59fdf9b0394 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Fri, 27 Oct 2023 20:12:15 +0800 Subject: [PATCH 079/104] Enhance FXUtils (Make tooltip installer faster). --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 59 +++----- .../jackhuang/hmcl/ui/TooltipInstaller.java | 130 ++++++++++++++++++ 2 files changed, 147 insertions(+), 42 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index c8af8a8a19..e024d474e1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -74,8 +74,6 @@ import java.awt.*; import java.io.*; import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -230,23 +228,22 @@ public static Node wrapMargin(Node node, Insets insets) { return new StackPane(node); } - public static void setValidateWhileTextChanged(Node field, boolean validate) { - if (field instanceof JFXTextField) { - if (validate) { - addListener(field, "FXUtils.validation", ((JFXTextField) field).textProperty(), o -> ((JFXTextField) field).validate()); - } else { - removeListener(field, "FXUtils.validation"); - } - ((JFXTextField) field).validate(); - } else if (field instanceof JFXPasswordField) { - if (validate) { - addListener(field, "FXUtils.validation", ((JFXPasswordField) field).textProperty(), o -> ((JFXPasswordField) field).validate()); - } else { - removeListener(field, "FXUtils.validation"); - } - ((JFXPasswordField) field).validate(); - } else - throw new IllegalArgumentException("Only JFXTextField and JFXPasswordField allowed"); + public static void setValidateWhileTextChanged(JFXTextField field, boolean validate) { + if (validate) { + addListener(field, "FXUtils.validation", field.textProperty(), o -> field.validate()); + } else { + removeListener(field, "FXUtils.validation"); + } + field.validate(); + } + + public static void setValidateWhileTextChanged(JFXPasswordField field, boolean validate) { + if (validate) { + addListener(field, "FXUtils.validation", field.textProperty(), o -> field.validate()); + } else { + removeListener(field, "FXUtils.validation"); + } + field.validate(); } public static boolean getValidateWhileTextChanged(Node field) { @@ -318,29 +315,7 @@ public static void installSlowTooltip(Node node, String tooltip) { } public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - runInFX(() -> { - try { - // Java 8 - Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); - Constructor behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); - behaviorConstructor.setAccessible(true); - Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false); - Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); - installMethod.setAccessible(true); - installMethod.invoke(behavior, node, tooltip); - } catch (ReflectiveOperationException e) { - try { - // Java 9 - Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay)); - Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay)); - Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay)); - } catch (ReflectiveOperationException e2) { - e.addSuppressed(e2); - Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e); - } - Tooltip.install(node, tooltip); - } - }); + runInFX(() -> TooltipInstaller.install(node, openDelay, visibleDelay, closeDelay, tooltip)); } public static void playAnimation(Node node, String animationKey, Timeline timeline) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java new file mode 100644 index 0000000000..a9f782bdb3 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java @@ -0,0 +1,130 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui; + +import javafx.scene.Node; +import javafx.scene.control.Tooltip; +import javafx.util.Duration; +import org.jackhuang.hmcl.util.Logging; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.logging.Level; + +public abstract class TooltipInstaller { + // Fallback: Java8TooltipInstaller -> Java9TooltipInstaller -> NoDurationTooltipInstaller + + private static final class Java8TooltipInstaller extends TooltipInstaller { + public static TooltipInstaller of() { + try { + // Java 8 + Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); + Constructor behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); + behaviorConstructor.setAccessible(true); + Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); + installMethod.setAccessible(true); + return new Java8TooltipInstaller(behaviorConstructor, installMethod); + } catch (ReflectiveOperationException e) { + Logging.LOG.log(Level.WARNING, "Cannot use Java 8 Tooltip Installer, fallback to Java 9 Tooltip Installer.", e); + return Java9TooltipInstaller.of(); + } + } + + private final Constructor behaviorConstructor; + + private final Method installMethod; + + public Java8TooltipInstaller(Constructor behaviorConstructor, Method installMethod) { + this.behaviorConstructor = behaviorConstructor; + this.installMethod = installMethod; + } + + @Override + protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { + try { + installMethod.invoke( + behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false), + node, + tooltip + ); + + return this; + } catch (ReflectiveOperationException e) { + Logging.LOG.log(Level.WARNING, "Cannot use Java 8 Tooltip Installer, fallback to Java 9 Tooltip Installer.", e); + return Java9TooltipInstaller.of().installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); + } + } + } + + private static final class Java9TooltipInstaller extends TooltipInstaller { + public static TooltipInstaller of() { + try { + Method setShowDelay = Tooltip.class.getMethod("setShowDelay", Duration.class); + Method setShowDuration = Tooltip.class.getMethod("setShowDuration", Duration.class); + Method setHideDelay = Tooltip.class.getMethod("setHideDelay", Duration.class); + return new Java9TooltipInstaller(setShowDelay, setShowDuration, setHideDelay); + } catch (ReflectiveOperationException e) { + Logging.LOG.log(Level.WARNING, "Cannot use Java 9 Tooltip Installer, fallback to No Duration Tooltip Installer.", e); + return new NoDurationTooltipInstaller(); + } + } + + private final Method setShowDelayMethod; + private final Method setShowDurationMethod; + private final Method setHideDelayMethod; + + private Java9TooltipInstaller(Method setShowDelayMethod, Method setShowDurationMethod, Method setHideDelayMethod) { + this.setShowDelayMethod = setShowDelayMethod; + this.setShowDurationMethod = setShowDurationMethod; + this.setHideDelayMethod = setHideDelayMethod; + } + + @Override + protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { + try { + setShowDelayMethod.invoke(tooltip, new Duration(openDelay)); + setShowDurationMethod.invoke(tooltip, new Duration(visibleDelay)); + setHideDelayMethod.invoke(tooltip, new Duration(closeDelay)); + Tooltip.install(node, tooltip); + return this; + } catch (ReflectiveOperationException e) { + Logging.LOG.log(Level.WARNING, "Cannot use Java 9 Tooltip Installer, fallback to No Duration Tooltip Installer.", e); + return new NoDurationTooltipInstaller().installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); + } + } + } + + private static final class NoDurationTooltipInstaller extends TooltipInstaller { + @Override + protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { + Tooltip.install(node, tooltip); + return this; + } + } + + private static TooltipInstaller installer = null; + + // FXThread + public static void install(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { + FXUtils.checkFxUserThread(); + + installer = (installer == null ? Java8TooltipInstaller.of() : installer).installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); + } + + protected abstract TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip); +} From 97db7744ccb29f8e924a003b0e2890da1115837d Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 28 Oct 2023 21:35:13 +0800 Subject: [PATCH 080/104] Fix --- HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index e024d474e1..640135be0f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -731,7 +731,7 @@ public static Image newRemoteImage(String url, double requestedWidth, double req } } - // The file is unavailable or unreadable + // The file is unavailable or unreadable. remoteImageCache.remove(url); try { @@ -747,7 +747,9 @@ public static Image newRemoteImage(String url, double requestedWidth, double req Task.runAsync(() -> { Path newPath = Files.createTempFile("hmcl-net-resource-cache-", ".cache"); PNGJavaFXUtils.writeImage(image, newPath); - remoteImageCache.put(url, newPath); + if (remoteImageCache.putIfAbsent(url, newPath) != null) { + Files.delete(newPath); // The image has been loaded in another task. Delete the image here in order not to pollute the tmp folder. + } }).start(); } }); From dba600d0a160b38a07d459f32b2ec86a278c7d3a Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 28 Oct 2023 22:01:53 +0800 Subject: [PATCH 081/104] Fix --- .../main/java/org/jackhuang/hmcl/ui/FXUtils.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 640135be0f..39cde198e0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -46,6 +46,8 @@ import javafx.util.Callback; import javafx.util.Duration; import javafx.util.StringConverter; +import org.glavo.png.PNGType; +import org.glavo.png.PNGWriter; import org.glavo.png.javafx.PNGJavaFXUtils; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; @@ -746,7 +748,17 @@ public static Image newRemoteImage(String url, double requestedWidth, double req if (newValue.doubleValue() >= 1.0 && !image.isError() && image.getPixelReader() != null && image.getWidth() > 0.0 && image.getHeight() > 0.0) { Task.runAsync(() -> { Path newPath = Files.createTempFile("hmcl-net-resource-cache-", ".cache"); - PNGJavaFXUtils.writeImage(image, newPath); + try (OutputStream outputStream = Files.newOutputStream(newPath)) { + new PNGWriter(outputStream, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL).write(PNGJavaFXUtils.asArgbImage(image)); + } catch (IOException e) { + try { + Files.delete(newPath); + } catch (IOException e2) { + e2.addSuppressed(e); + throw e2; + } + throw e; + } if (remoteImageCache.putIfAbsent(url, newPath) != null) { Files.delete(newPath); // The image has been loaded in another task. Delete the image here in order not to pollute the tmp folder. } From e648a6d127c5858f7562ffb05c84acc5d7ec10e5 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 29 Oct 2023 12:12:30 +0800 Subject: [PATCH 082/104] Fix #2560 --- .../java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java | 2 +- .../org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 836e40988f..bb2ee9434e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -451,8 +451,8 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { if (empty) return; content.setTitle(dataItem.getTitle()); content.getTags().clear(); + content.getTags().add(dataItem.getModInfo().getModLoaderType().getLoaderName()); if (dataItem.getMod() != null) { - content.getTags().add(dataItem.getModInfo().getModLoaderType().getLoaderName()); if (I18n.getCurrentLocale().getLocale() == Locale.CHINA) { content.getTags().add(dataItem.getMod().getDisplayName()); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java index 5d3b343773..c74adfb247 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java @@ -136,7 +136,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSys } } return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), ModLoaderType.FORGE), modFile, mod.getDisplayName(), new LocalModFile.Description(mod.getDescription()), - mod.getAuthors(), mod.getVersion().replace("${file.jarVersion}", jarVersion), "", + mod.getAuthors(), jarVersion == null ? mod.getVersion() : mod.getVersion().replace("${file.jarVersion}", jarVersion), "", mod.getDisplayURL(), metadata.getLogoFile()); } From 2ea019631087ab579f4de5c84e51bf080aac4cb1 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Mon, 30 Oct 2023 22:23:33 +0800 Subject: [PATCH 083/104] Fix typo --- .../java/org/jackhuang/hmcl/util/ResourceNotFoundError.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java index 1977eeb81b..d404c0fcdd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/ResourceNotFoundError.java @@ -35,7 +35,7 @@ public ResourceNotFoundError(String message, Throwable cause) { public static InputStream getResourceAsStream(String url) { InputStream stream = ResourceNotFoundError.class.getResourceAsStream(url); if (stream == null) - throw new ResourceNotFoundError("RemoteResource not found: " + url); + throw new ResourceNotFoundError("Resource not found: " + url); return stream; } } From 76fb8f70339fc14a0a7db6f327d77f68588a1bb0 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Mon, 30 Oct 2023 23:15:52 +0800 Subject: [PATCH 084/104] Fix remote image cache. --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 39cde198e0..72dc20bb76 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -31,10 +31,10 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.control.*; import javafx.scene.control.ScrollPane; -import javafx.scene.image.*; +import javafx.scene.control.*; import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.input.*; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.Priority; @@ -79,8 +79,8 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; import java.util.List; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -726,8 +726,8 @@ public static Image newRemoteImage(String url, double requestedWidth, double req Path currentPath = remoteImageCache.get(url); if (currentPath != null) { if (Files.isReadable(currentPath)) { - try { - return new Image(Files.newInputStream(currentPath), requestedWidth, requestedHeight, preserveRatio, smooth); + try (InputStream inputStream = Files.newInputStream(currentPath)) { + return new Image(inputStream, requestedWidth, requestedHeight, preserveRatio, smooth); } catch (IOException e) { LOG.log(Level.WARNING, "An exception encountered while reading data from cached image file.", e); } @@ -748,8 +748,11 @@ public static Image newRemoteImage(String url, double requestedWidth, double req if (newValue.doubleValue() >= 1.0 && !image.isError() && image.getPixelReader() != null && image.getWidth() > 0.0 && image.getHeight() > 0.0) { Task.runAsync(() -> { Path newPath = Files.createTempFile("hmcl-net-resource-cache-", ".cache"); - try (OutputStream outputStream = Files.newOutputStream(newPath)) { - new PNGWriter(outputStream, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL).write(PNGJavaFXUtils.asArgbImage(image)); + try ( // Make sure the file is released from JVM before we put the path into remoteImageCache. + OutputStream outputStream = Files.newOutputStream(newPath); + PNGWriter writer = new PNGWriter(outputStream, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL) + ) { + writer.write(PNGJavaFXUtils.asArgbImage(image)); } catch (IOException e) { try { Files.delete(newPath); From 873af707b4d9374262ce3e2a747d4a74b8f8c9a3 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Tue, 31 Oct 2023 19:11:45 +0800 Subject: [PATCH 085/104] Fix javadoc --- HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 72dc20bb76..41270b082f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -698,8 +698,6 @@ public static Image newBuiltinImage(String url, double requestedWidth, double re * * @param url the url of image. The image resource should be a file on the internet. * @return the image resource within the jar. - * @see org.jackhuang.hmcl.util.CrashReporter - * @see ResourceNotFoundError */ public static Image newRemoteImage(String url) { return newRemoteImage(url, 0, 0, false, false, false); @@ -719,8 +717,6 @@ public static Image newRemoteImage(String url) { * algorithm or a faster one when scaling this image to fit within * the specified bounding box * @return the image resource within the jar. - * @see CrashReporter - * @see ResourceNotFoundError */ public static Image newRemoteImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth, boolean backgroundLoading) { Path currentPath = remoteImageCache.get(url); From 961655cdfee8ae4e9fa3e5fc0c9e753421dc345d Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 5 Nov 2023 13:04:00 +0800 Subject: [PATCH 086/104] Fix checkstyle --- HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 41270b082f..4b1d0ca42b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -53,7 +53,6 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.construct.JFXHyperlink; -import org.jackhuang.hmcl.util.CrashReporter; import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.ResourceNotFoundError; From 798adab63186da1fd509112cf3da834892ca337a Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 5 Nov 2023 14:01:02 +0800 Subject: [PATCH 087/104] Optimize FXUtils::shutdown --- HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 4b1d0ca42b..0b1d50444a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -103,14 +103,13 @@ private FXUtils() { private static final Map remoteImageCache = new ConcurrentHashMap<>(); public static void shutdown() { - for (String url : remoteImageCache.keySet()) { - Path path = remoteImageCache.get(url); + for (Map.Entry entry: remoteImageCache.entrySet()) { try { - Files.deleteIfExists(path); + Files.deleteIfExists(entry.getValue()); } catch (IOException e) { - LOG.log(Level.WARNING, String.format("Failed to delete cache file %s.", path), e); + LOG.log(Level.WARNING, String.format("Failed to delete cache file %s.", entry.getValue()), e); } - remoteImageCache.remove(url); + remoteImageCache.remove(entry.getKey()); } builtinImageCache.clear(); From 3c303d7de6b28a1a2715ea8e28c0670bcb8ddffe Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sun, 5 Nov 2023 17:25:37 +0800 Subject: [PATCH 088/104] Support NeoForged 1.20.2 --- .../hmcl/setting/VersionIconType.java | 2 +- .../org/jackhuang/hmcl/ui/InstallerItem.java | 4 +- .../hmcl/ui/construct/TaskListPane.java | 4 +- .../hmcl/ui/versions/DownloadPage.java | 2 +- .../hmcl/ui/versions/ModListPageSkin.java | 4 +- .../img/{neoforged.png => neoforge.png} | Bin .../resources/assets/lang/I18N.properties | 2 +- .../resources/assets/lang/I18N_zh.properties | 2 +- .../assets/lang/I18N_zh_CN.properties | 2 +- .../download/BMCLAPIDownloadProvider.java | 2 +- .../hmcl/download/LibraryAnalyzer.java | 2 +- .../hmcl/download/MojangDownloadProvider.java | 2 +- .../download/forge/ForgeNewInstallTask.java | 2 +- .../neoforge/NeoForgedInstallTask.java | 42 +- .../neoforge/NeoForgedOldInstallTask.java | 424 ++++++++++++++++++ 15 files changed, 468 insertions(+), 28 deletions(-) rename HMCL/src/main/resources/assets/img/{neoforged.png => neoforge.png} (100%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedOldInstallTask.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java index 85afe151b0..0d8b1595a9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java @@ -27,7 +27,7 @@ public enum VersionIconType { CRAFT_TABLE("/assets/img/craft_table.png"), FABRIC("/assets/img/fabric.png"), FORGE("/assets/img/forge.png"), - NEO_FORGED("/assets/img/neoforged.png"), + NEO_FORGED("/assets/img/neoforge.png"), FURNACE("/assets/img/furnace.png"), QUILT("/assets/img/quilt.png"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index 8036ba0003..b72f07487f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -92,8 +92,8 @@ public InstallerItem(String id) { case "quilt-api": imageUrl = "/assets/img/quilt.png"; break; - case "neoforged": - imageUrl = "/assets/img/neoforged.png"; + case "neoforge": + imageUrl = "/assets/img/neoforge.png"; break; default: imageUrl = null; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index fe0f09de1a..bcfbffd405 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -125,7 +125,7 @@ public void onRunning(Task task) { } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.forge"))); } else if (task instanceof NeoForgedInstallTask) { - task.setName(i18n("install.installer.install", i18n("install.installer.neoforged"))); + task.setName(i18n("install.installer.install", i18n("install.installer.neoforge"))); } else if (task instanceof LiteLoaderInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.liteloader"))); } else if (task instanceof OptiFineInstallTask) { @@ -251,7 +251,7 @@ public StageNode(String stage) { case "hmcl.install.assets": message = i18n("assets.download"); break; case "hmcl.install.game": message = i18n("install.installer.install", i18n("install.installer.game") + " " + stageValue); break; case "hmcl.install.forge": message = i18n("install.installer.install", i18n("install.installer.forge") + " " + stageValue); break; - case "hmcl.install.neoforged": message = i18n("install.installer.install", i18n("install.installer.neoforged") + " " + stageValue); break; + case "hmcl.install.neoforge": message = i18n("install.installer.install", i18n("install.installer.neoforge") + " " + stageValue); break; case "hmcl.install.liteloader": message = i18n("install.installer.install", i18n("install.installer.liteloader") + " " + stageValue); break; case "hmcl.install.optifine": message = i18n("install.installer.install", i18n("install.installer.optifine") + " " + stageValue); break; case "hmcl.install.fabric": message = i18n("install.installer.install", i18n("install.installer.fabric") + " " + stageValue); break; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index e88398f463..2a8f8a74f0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -400,7 +400,7 @@ private static final class ModItem extends StackPane { content.getTags().add(i18n("install.installer.forge")); break; case NEO_FORGED: - content.getTags().add(i18n("install.installer.neoforged")); + content.getTags().add(i18n("install.installer.neoforge")); break; case FABRIC: content.getTags().add(i18n("install.installer.fabric")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index c8010a14a8..b721b0009c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -335,7 +335,7 @@ class ModInfoDialog extends JFXDialogLayout { loaderName = i18n("install.installer.forge"); break; case NEO_FORGED: - loaderName = i18n("install.installer.neoforged"); + loaderName = i18n("install.installer.neoforge"); break; case FABRIC: loaderName = i18n("install.installer.fabric"); @@ -470,7 +470,7 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { content.getTags().add(i18n("install.installer.forge")); break; case NEO_FORGED: - content.getTags().add(i18n("install.installer.neoforged")); + content.getTags().add(i18n("install.installer.neoforge")); break; case FABRIC: content.getTags().add(i18n("install.installer.fabric")); diff --git a/HMCL/src/main/resources/assets/img/neoforged.png b/HMCL/src/main/resources/assets/img/neoforge.png similarity index 100% rename from HMCL/src/main/resources/assets/img/neoforged.png rename to HMCL/src/main/resources/assets/img/neoforge.png diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 258433d4e1..9d8048b4df 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -618,7 +618,7 @@ install.installer.fabric=Fabric install.installer.fabric-api=Fabric API install.installer.fabric-api.warning=Warning: Fabric API is a mod, and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not work. If you do want to change these settings, you should reinstall it. install.installer.forge=Forge -install.installer.neoforged=NeoForge +install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=Incompatible with %s install.installer.install=Install %s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 2c82fc2502..83e9053081 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -496,7 +496,7 @@ install.installer.fabric=Fabric install.installer.fabric-api=Fabric API install.installer.fabric-api.warning=警告:Fabric API 是 Mod,將會被安裝到新遊戲的 Mod 資料夾,請你在安裝遊戲後不要修改當前遊戲的版本隔離/遊戲運行路徑設置,如果你在之後修改了相關設置,Fabric API 需要被重新安裝。 install.installer.forge=Forge -install.installer.neoforged=NeoForge +install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=與 %s 不相容 install.installer.install=安裝%s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 4d6b49a34b..4e39559710 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -495,7 +495,7 @@ install.installer.fabric=Fabric install.installer.fabric-api=Fabric API install.installer.fabric-api.warning=警告:Fabric API 是一个 Mod,将会被安装到新游戏的 Mod 文件夹,请你在安装游戏后不要修改当前游戏的版本隔离/游戏运行路径设置,如果你在之后修改了相关设置,Fabric API 需要被重新安装。 install.installer.forge=Forge -install.installer.neoforged=NeoForge +install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=与 %s 不兼容 install.installer.install=安装 %s diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java index cfcc3b3514..b342c9cd42 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -81,7 +81,7 @@ public VersionList getVersionListById(String id) { return fabricApi; case "forge": return forge; - case "neoforged": + case "neoforge": return neoforged; case "liteloader": return liteLoader; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index d0ba6ae760..aafbae461e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -187,7 +187,7 @@ public String patchVersion(String libraryVersion) { return super.patchVersion(libraryVersion); } }, - NEO_FORGED(true, "neoforged", Pattern.compile("net\\.minecraftforge"), Pattern.compile("(forge|fmlloader)"), ModLoaderType.NEO_FORGED) { + NEO_FORGED(true, "neoforge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("(forge|fmlloader)"), ModLoaderType.NEO_FORGED) { private final Pattern NEO_FORGE_VERSION_MATCHER = Pattern.compile("^([0-9.]+)-(?[0-9.]+)(-([0-9.]+))?$"); @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java index 091210409b..23e8307caf 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -77,7 +77,7 @@ public VersionList getVersionListById(String id) { return fabricApi; case "forge": return forge; - case "neoforged": + case "neoforge": return neoforged; case "liteloader": return liteLoader; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index 7fc1584922..8c96ba321e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -192,7 +192,7 @@ public void execute() throws Exception { private Path tempDir; private AtomicInteger processorDoneCount = new AtomicInteger(0); - ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) { + public ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) { this.dependencyManager = dependencyManager; this.gameRepository = dependencyManager.getGameRepository(); this.version = version; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java index ece6a3edef..7384dab44f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java @@ -3,7 +3,7 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.VersionMismatchException; -import org.jackhuang.hmcl.download.forge.ForgeInstallTask; +import org.jackhuang.hmcl.download.forge.*; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; @@ -17,6 +17,9 @@ import java.nio.file.Path; import java.util.*; +import static org.jackhuang.hmcl.util.StringUtils.removePrefix; +import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; + public final class NeoForgedInstallTask extends Task { private final DefaultDependencyManager dependencyManager; @@ -84,21 +87,34 @@ public static Task install(DefaultDependencyManager dependencyManager, Optional gameVersion = dependencyManager.getGameRepository().getGameVersion(version); if (!gameVersion.isPresent()) throw new IOException(); try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - Map installProfile = JsonUtils.fromNonNullJson(FileUtils.readText(fs.getPath("install_profile.json")), Map.class); - Object p = installProfile.get("profile"); - if (!LibraryAnalyzer.LibraryType.FORGE.getPatchId().equals(p)) { - throw new IOException(); - } - if (!Files.exists(fs.getPath("META-INF/NEOFORGE.RSA"))) { + String installProfileText = FileUtils.readText(fs.getPath("install_profile.json")); + Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); + if (LibraryAnalyzer.LibraryType.FORGE.getPatchId().equals(installProfile.get("profile")) && Files.exists(fs.getPath("META-INF/NEOFORGE.RSA"))) { + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + if (!gameVersion.get().equals(profile.getMinecraft())) + throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get()); + return new ForgeNewInstallTask(dependencyManager, version, modifyNeoForgedOldVersion(gameVersion.get(), profile.getVersion()), installer).thenApplyAsync(neoForgeVersion -> { + if (!neoForgeVersion.getId().equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) || neoForgeVersion.getVersion() == null) { + throw new IOException("Invalid neoforged version."); + } + return neoForgeVersion.setId(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId()).setVersion(neoForgeVersion.getVersion().replace(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId())); + }); + } else if (LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId().equals(installProfile.get("profile"))) { + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + if (!gameVersion.get().equals(profile.getMinecraft())) + throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get()); + return new NeoForgedOldInstallTask(dependencyManager, version, modifyNeoForgedNewVersion(profile.getVersion()), installer); + } else { throw new IOException(); } } + } - return ForgeInstallTask.install(dependencyManager, version, installer).thenApplyAsync(neoForgeVersion -> { - if (!neoForgeVersion.getId().equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) || neoForgeVersion.getVersion() == null) { - throw new IOException("Invalid neoforged version."); - } - return neoForgeVersion.setId(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId()).setVersion(neoForgeVersion.getVersion().replace(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId())); - }); + private static String modifyNeoForgedOldVersion(String gameVersion, String version) { + return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"); + } + + private static String modifyNeoForgedNewVersion(String version) { + return removePrefix(version.replace("neoforge", ""), "-"); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedOldInstallTask.java new file mode 100644 index 0000000000..eccbc74a6e --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedOldInstallTask.java @@ -0,0 +1,424 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.neoforge; + +import org.jackhuang.hmcl.download.ArtifactMalformedException; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask; +import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.DigestUtils; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.function.ExceptionalFunction; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.ChecksumMismatchException; +import org.jackhuang.hmcl.util.io.CompressingUtils; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.platform.CommandBuilder; +import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.SystemUtils; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.zip.ZipException; + +import static org.jackhuang.hmcl.util.Logging.LOG; +import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; + +public class NeoForgedOldInstallTask extends Task { + + private class ProcessorTask extends Task { + + private final Processor processor; + private final Map vars; + + public ProcessorTask(@NotNull Processor processor, @NotNull Map vars) { + this.processor = processor; + this.vars = vars; + setSignificance(TaskSignificance.MODERATE); + } + + @Override + public void execute() throws Exception { + Map outputs = new HashMap<>(); + boolean miss = false; + + for (Map.Entry entry : processor.getOutputs().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + key = parseLiteral(key, vars); + value = parseLiteral(value, vars); + + if (key == null || value == null) { + throw new ArtifactMalformedException("Invalid forge installation configuration"); + } + + outputs.put(key, value); + + Path artifact = Paths.get(key); + if (Files.exists(artifact)) { + String code; + try (InputStream stream = Files.newInputStream(artifact)) { + code = (DigestUtils.digestToString("SHA-1", stream)); + } + + if (!Objects.equals(code, value)) { + Files.delete(artifact); + LOG.info("Found existing file is not valid: " + artifact); + + miss = true; + } + } else { + miss = true; + } + } + + if (!processor.getOutputs().isEmpty() && !miss) { + return; + } + + Path jar = gameRepository.getArtifactFile(version, processor.getJar()); + if (!Files.isRegularFile(jar)) + throw new FileNotFoundException("Game processor file not found, should be downloaded in preprocess"); + + String mainClass; + try (JarFile jarFile = new JarFile(jar.toFile())) { + mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); + } + + if (StringUtils.isBlank(mainClass)) + throw new Exception("Game processor jar does not have main class " + jar); + + List command = new ArrayList<>(); + command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString()); + command.add("-cp"); + + List classpath = new ArrayList<>(processor.getClasspath().size() + 1); + for (Artifact artifact : processor.getClasspath()) { + Path file = gameRepository.getArtifactFile(version, artifact); + if (!Files.isRegularFile(file)) + throw new Exception("Game processor dependency missing"); + classpath.add(file.toString()); + } + classpath.add(jar.toString()); + command.add(String.join(OperatingSystem.PATH_SEPARATOR, classpath)); + + command.add(mainClass); + + List args = new ArrayList<>(processor.getArgs().size()); + for (String arg : processor.getArgs()) { + String parsed = parseLiteral(arg, vars); + if (parsed == null) + throw new ArtifactMalformedException("Invalid forge installation configuration"); + args.add(parsed); + } + + command.addAll(args); + + LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); + int exitCode = SystemUtils.callExternalProcess(command); + if (exitCode != 0) + throw new IOException("Game processor exited abnormally with code " + exitCode); + + for (Map.Entry entry : outputs.entrySet()) { + Path artifact = Paths.get(entry.getKey()); + if (!Files.isRegularFile(artifact)) + throw new FileNotFoundException("File missing: " + artifact); + + String code; + try (InputStream stream = Files.newInputStream(artifact)) { + code = DigestUtils.digestToString("SHA-1", stream); + } + + if (!Objects.equals(code, entry.getValue())) { + Files.delete(artifact); + throw new ChecksumMismatchException("SHA-1", entry.getValue(), code); + } + } + } + } + + private final DefaultDependencyManager dependencyManager; + private final DefaultGameRepository gameRepository; + private final Version version; + private final Path installer; + private final List> dependents = new ArrayList<>(1); + private final List> dependencies = new ArrayList<>(1); + + private ForgeNewInstallProfile profile; + private List processors; + private Version neoForgeVersion; + private final String selfVersion; + + private Path tempDir; + private AtomicInteger processorDoneCount = new AtomicInteger(0); + + NeoForgedOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) { + this.dependencyManager = dependencyManager; + this.gameRepository = dependencyManager.getGameRepository(); + this.version = version; + this.installer = installer; + this.selfVersion = selfVersion; + + setSignificance(TaskSignificance.MAJOR); + } + + private static String replaceTokens(Map tokens, String value) { + StringBuilder buf = new StringBuilder(); + for (int x = 0; x < value.length(); x++) { + char c = value.charAt(x); + if (c == '\\') { + if (x == value.length() - 1) + throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value); + buf.append(value.charAt(++x)); + } else if (c == '{' || c == '\'') { + StringBuilder key = new StringBuilder(); + for (int y = x + 1; y <= value.length(); y++) { + if (y == value.length()) + throw new IllegalArgumentException("Illegal pattern (Unclosed " + c + "): " + value); + char d = value.charAt(y); + if (d == '\\') { + if (y == value.length() - 1) + throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value); + key.append(value.charAt(++y)); + } else { + if (c == '{' && d == '}') { + x = y; + break; + } + if (c == '\'' && d == '\'') { + x = y; + break; + } + key.append(d); + } + } + if (c == '\'') { + buf.append(key); + } else { + if (!tokens.containsKey(key.toString())) + throw new IllegalArgumentException("Illegal pattern: " + value + " Missing Key: " + key); + buf.append(tokens.get(key.toString())); + } + } else { + buf.append(c); + } + } + return buf.toString(); + } + + private String parseLiteral(String literal, Map var, ExceptionalFunction plainConverter) throws E { + if (StringUtils.isSurrounded(literal, "{", "}")) + return var.get(StringUtils.removeSurrounding(literal, "{", "}")); + else if (StringUtils.isSurrounded(literal, "'", "'")) + return StringUtils.removeSurrounding(literal, "'"); + else if (StringUtils.isSurrounded(literal, "[", "]")) + return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, "[", "]"))).toString(); + else + return plainConverter.apply(replaceTokens(var, literal)); + } + + private String parseLiteral(String literal, Map var) { + return parseLiteral(literal, var, ExceptionalFunction.identity()); + } + + @Override + public Collection> getDependents() { + return dependents; + } + + @Override + public Collection> getDependencies() { + return dependencies; + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + profile = JsonUtils.fromNonNullJson(FileUtils.readText(fs.getPath("install_profile.json")), ForgeNewInstallProfile.class); + processors = profile.getProcessors(); + neoForgeVersion = JsonUtils.fromNonNullJson(FileUtils.readText(fs.getPath(profile.getJson())), Version.class); + + for (Library library : profile.getLibraries()) { + Path file = fs.getPath("maven").resolve(library.getPath()); + if (Files.exists(file)) { + Path dest = gameRepository.getLibraryFile(version, library).toPath(); + FileUtils.copyFile(file, dest); + } + } + + if (profile.getPath().isPresent()) { + Path mainJar = profile.getPath().get().getPath(fs.getPath("maven")); + if (Files.exists(mainJar)) { + Path dest = gameRepository.getArtifactFile(version, profile.getPath().get()); + FileUtils.copyFile(mainJar, dest); + } + } + } catch (ZipException ex) { + throw new ArtifactMalformedException("Malformed forge installer file", ex); + } + + dependents.add(new GameLibrariesTask(dependencyManager, version, true, profile.getLibraries())); + } + + private Map parseOptions(List args, Map vars) { + Map options = new LinkedHashMap<>(); + String optionName = null; + for (String arg : args) { + if (arg.startsWith("--")) { + if (optionName != null) { + options.put(optionName, ""); + } + optionName = arg.substring(2); + } else { + if (optionName == null) { + // ignore + } else { + options.put(optionName, parseLiteral(arg, vars)); + optionName = null; + } + } + } + if (optionName != null) { + options.put(optionName, ""); + } + return options; + } + + private Task patchDownloadMojangMappingsTask(Processor processor, Map vars) { + Map options = parseOptions(processor.getArgs(), vars); + if (!"DOWNLOAD_MOJMAPS".equals(options.get("task")) || !"client".equals(options.get("side"))) + return null; + String version = options.get("version"); + String output = options.get("output"); + if (version == null || output == null) + return null; + + LOG.info("Patching DOWNLOAD_MOJMAPS task"); + return new VersionJsonDownloadTask(version, dependencyManager) + .thenComposeAsync(json -> { + DownloadInfo mappings = fromNonNullJson(json, Version.class) + .getDownloads().get(DownloadType.CLIENT_MAPPINGS); + if (mappings == null) { + throw new Exception("client_mappings download info not found"); + } + + List mappingsUrl = dependencyManager.getDownloadProvider() + .injectURLWithCandidates(mappings.getUrl()); + FileDownloadTask mappingsTask = new FileDownloadTask( + mappingsUrl, + new File(output), + IntegrityCheck.of("SHA-1", mappings.getSha1())); + mappingsTask.setCaching(true); + mappingsTask.setCacheRepository(dependencyManager.getCacheRepository()); + return mappingsTask; + }); + } + + private Task createProcessorTask(Processor processor, Map vars) { + Task task = patchDownloadMojangMappingsTask(processor, vars); + if (task == null) { + task = new ProcessorTask(processor, vars); + } + task.onDone().register( + () -> updateProgress(processorDoneCount.incrementAndGet(), processors.size())); + return task; + } + + @Override + public void execute() throws Exception { + tempDir = Files.createTempDirectory("neoforge_installer"); + + Map vars = new HashMap<>(); + + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + for (Map.Entry entry : profile.getData().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + vars.put(key, parseLiteral(value, + Collections.emptyMap(), + str -> { + Path dest = Files.createTempFile(tempDir, null, null); + FileUtils.copyFile(fs.getPath(str), dest); + return dest.toString(); + })); + } + } catch (ZipException ex) { + throw new ArtifactMalformedException("Malformed neoforge installer file", ex); + } + + vars.put("SIDE", "client"); + vars.put("MINECRAFT_JAR", gameRepository.getVersionJar(version).getAbsolutePath()); + vars.put("MINECRAFT_VERSION", gameRepository.getVersionJar(version).getAbsolutePath()); + vars.put("ROOT", gameRepository.getBaseDirectory().getAbsolutePath()); + vars.put("INSTALLER", installer.toAbsolutePath().toString()); + vars.put("LIBRARY_DIR", gameRepository.getLibrariesDirectory(version).getAbsolutePath()); + + updateProgress(0, processors.size()); + + Task processorsTask = Task.runSequentially( + processors.stream() + .map(processor -> createProcessorTask(processor, vars)) + .toArray(Task[]::new)); + + dependencies.add( + processorsTask.thenComposeAsync( + dependencyManager.checkLibraryCompletionAsync(neoForgeVersion, true))); + + setResult(neoForgeVersion + .setPriority(30000) + .setId(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId()) + .setVersion(selfVersion)); + } + + @Override + public boolean doPostExecute() { + return true; + } + + @Override + public void postExecute() throws Exception { + FileUtils.deleteDirectory(tempDir.toFile()); + } +} From e7782e8d513cb211a89df215ae98a9a8b90ca0d3 Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 11 Nov 2023 17:53:59 +0800 Subject: [PATCH 089/104] Fix merge --- HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java | 2 +- data-json/dynamic-remote-resources.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java index 82120a687d..6761ad9866 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java @@ -27,8 +27,8 @@ import javafx.scene.layout.StackPane; import javafx.stage.Stage; import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; import org.jackhuang.hmcl.countly.CrashReport; +import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; diff --git a/data-json/dynamic-remote-resources.json b/data-json/dynamic-remote-resources.json index bb0e55c236..c82b21a511 100644 --- a/data-json/dynamic-remote-resources.json +++ b/data-json/dynamic-remote-resources.json @@ -4,14 +4,14 @@ "1": { "url": "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", "local_path": "HMCL/src/main/resources/assets/mod_data.txt", - "sha1": "0ae36a65a00b00176358bd6b0d3c8787b3668c23" + "sha1": "2ffa59fc1b3736257c4dc38e67e4a3b5509be481" } }, "modpack_data": { "1": { "url": "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", "local_path": "HMCL/src/main/resources/assets/modpack_data.txt", - "sha1": "b0e771db170835e1154da4c21b7417a688836162" + "sha1": "483ad6aa675a0f2ac01c5522c7e0cdc55ef53121" } } } From b60cf41a1ec5ed0274d89775590f3bb1b08efeca Mon Sep 17 00:00:00 2001 From: Burning_TNT Date: Sat, 11 Nov 2023 18:00:49 +0800 Subject: [PATCH 090/104] I have no idea on why the sha1 was matched. --- data-json/dynamic-remote-resources.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data-json/dynamic-remote-resources.json b/data-json/dynamic-remote-resources.json index c82b21a511..bb0e55c236 100644 --- a/data-json/dynamic-remote-resources.json +++ b/data-json/dynamic-remote-resources.json @@ -4,14 +4,14 @@ "1": { "url": "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", "local_path": "HMCL/src/main/resources/assets/mod_data.txt", - "sha1": "2ffa59fc1b3736257c4dc38e67e4a3b5509be481" + "sha1": "0ae36a65a00b00176358bd6b0d3c8787b3668c23" } }, "modpack_data": { "1": { "url": "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", "local_path": "HMCL/src/main/resources/assets/modpack_data.txt", - "sha1": "483ad6aa675a0f2ac01c5522c7e0cdc55ef53121" + "sha1": "b0e771db170835e1154da4c21b7417a688836162" } } } From 9bc05236f976d50fc6a0da33e85b61e5cf29e617 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Mon, 27 Nov 2023 19:59:24 +0800 Subject: [PATCH 091/104] Revert "Enhance FXUtils (Make tooltip installer faster)." This reverts commit 0a49eb2c1204e4be7dc0df3084faa59fdf9b0394. --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 59 +++++--- .../jackhuang/hmcl/ui/TooltipInstaller.java | 130 ------------------ 2 files changed, 42 insertions(+), 147 deletions(-) delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 0b1d50444a..db475d464b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -75,6 +75,8 @@ import java.awt.*; import java.io.*; import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -228,22 +230,23 @@ public static Node wrapMargin(Node node, Insets insets) { return new StackPane(node); } - public static void setValidateWhileTextChanged(JFXTextField field, boolean validate) { - if (validate) { - addListener(field, "FXUtils.validation", field.textProperty(), o -> field.validate()); - } else { - removeListener(field, "FXUtils.validation"); - } - field.validate(); - } - - public static void setValidateWhileTextChanged(JFXPasswordField field, boolean validate) { - if (validate) { - addListener(field, "FXUtils.validation", field.textProperty(), o -> field.validate()); - } else { - removeListener(field, "FXUtils.validation"); - } - field.validate(); + public static void setValidateWhileTextChanged(Node field, boolean validate) { + if (field instanceof JFXTextField) { + if (validate) { + addListener(field, "FXUtils.validation", ((JFXTextField) field).textProperty(), o -> ((JFXTextField) field).validate()); + } else { + removeListener(field, "FXUtils.validation"); + } + ((JFXTextField) field).validate(); + } else if (field instanceof JFXPasswordField) { + if (validate) { + addListener(field, "FXUtils.validation", ((JFXPasswordField) field).textProperty(), o -> ((JFXPasswordField) field).validate()); + } else { + removeListener(field, "FXUtils.validation"); + } + ((JFXPasswordField) field).validate(); + } else + throw new IllegalArgumentException("Only JFXTextField and JFXPasswordField allowed"); } public static boolean getValidateWhileTextChanged(Node field) { @@ -315,7 +318,29 @@ public static void installSlowTooltip(Node node, String tooltip) { } public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - runInFX(() -> TooltipInstaller.install(node, openDelay, visibleDelay, closeDelay, tooltip)); + runInFX(() -> { + try { + // Java 8 + Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); + Constructor behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); + behaviorConstructor.setAccessible(true); + Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false); + Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); + installMethod.setAccessible(true); + installMethod.invoke(behavior, node, tooltip); + } catch (ReflectiveOperationException e) { + try { + // Java 9 + Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay)); + Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay)); + Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay)); + } catch (ReflectiveOperationException e2) { + e.addSuppressed(e2); + Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e); + } + Tooltip.install(node, tooltip); + } + }); } public static void playAnimation(Node node, String animationKey, Timeline timeline) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java deleted file mode 100644 index a9f782bdb3..0000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2021 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.ui; - -import javafx.scene.Node; -import javafx.scene.control.Tooltip; -import javafx.util.Duration; -import org.jackhuang.hmcl.util.Logging; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.logging.Level; - -public abstract class TooltipInstaller { - // Fallback: Java8TooltipInstaller -> Java9TooltipInstaller -> NoDurationTooltipInstaller - - private static final class Java8TooltipInstaller extends TooltipInstaller { - public static TooltipInstaller of() { - try { - // Java 8 - Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); - Constructor behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); - behaviorConstructor.setAccessible(true); - Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); - installMethod.setAccessible(true); - return new Java8TooltipInstaller(behaviorConstructor, installMethod); - } catch (ReflectiveOperationException e) { - Logging.LOG.log(Level.WARNING, "Cannot use Java 8 Tooltip Installer, fallback to Java 9 Tooltip Installer.", e); - return Java9TooltipInstaller.of(); - } - } - - private final Constructor behaviorConstructor; - - private final Method installMethod; - - public Java8TooltipInstaller(Constructor behaviorConstructor, Method installMethod) { - this.behaviorConstructor = behaviorConstructor; - this.installMethod = installMethod; - } - - @Override - protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - try { - installMethod.invoke( - behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false), - node, - tooltip - ); - - return this; - } catch (ReflectiveOperationException e) { - Logging.LOG.log(Level.WARNING, "Cannot use Java 8 Tooltip Installer, fallback to Java 9 Tooltip Installer.", e); - return Java9TooltipInstaller.of().installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); - } - } - } - - private static final class Java9TooltipInstaller extends TooltipInstaller { - public static TooltipInstaller of() { - try { - Method setShowDelay = Tooltip.class.getMethod("setShowDelay", Duration.class); - Method setShowDuration = Tooltip.class.getMethod("setShowDuration", Duration.class); - Method setHideDelay = Tooltip.class.getMethod("setHideDelay", Duration.class); - return new Java9TooltipInstaller(setShowDelay, setShowDuration, setHideDelay); - } catch (ReflectiveOperationException e) { - Logging.LOG.log(Level.WARNING, "Cannot use Java 9 Tooltip Installer, fallback to No Duration Tooltip Installer.", e); - return new NoDurationTooltipInstaller(); - } - } - - private final Method setShowDelayMethod; - private final Method setShowDurationMethod; - private final Method setHideDelayMethod; - - private Java9TooltipInstaller(Method setShowDelayMethod, Method setShowDurationMethod, Method setHideDelayMethod) { - this.setShowDelayMethod = setShowDelayMethod; - this.setShowDurationMethod = setShowDurationMethod; - this.setHideDelayMethod = setHideDelayMethod; - } - - @Override - protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - try { - setShowDelayMethod.invoke(tooltip, new Duration(openDelay)); - setShowDurationMethod.invoke(tooltip, new Duration(visibleDelay)); - setHideDelayMethod.invoke(tooltip, new Duration(closeDelay)); - Tooltip.install(node, tooltip); - return this; - } catch (ReflectiveOperationException e) { - Logging.LOG.log(Level.WARNING, "Cannot use Java 9 Tooltip Installer, fallback to No Duration Tooltip Installer.", e); - return new NoDurationTooltipInstaller().installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); - } - } - } - - private static final class NoDurationTooltipInstaller extends TooltipInstaller { - @Override - protected TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - Tooltip.install(node, tooltip); - return this; - } - } - - private static TooltipInstaller installer = null; - - // FXThread - public static void install(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - FXUtils.checkFxUserThread(); - - installer = (installer == null ? Java8TooltipInstaller.of() : installer).installTooltip(node, openDelay, visibleDelay, closeDelay, tooltip); - } - - protected abstract TooltipInstaller installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip); -} From f9518644d4af2597eb5c36d8c202cc24712edd81 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Wed, 29 Nov 2023 20:58:35 +0800 Subject: [PATCH 092/104] Support multi download source in order balance the traffic of hmcl.huangyuhui.net and the download speed in China Mainland. --- .../java/org/jackhuang/hmcl/Metadata.java | 2 +- .../resource/RemoteResourceManager.java | 20 ++++++++++--------- data-json/dynamic-remote-resources.json | 10 ++++++++-- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 304ccf6281..b84ef0b36a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -39,7 +39,7 @@ private Metadata() {} // hmcl.update_source.override is deprecated. If it is used, a warning message will be printed in org.jackhuang.hmcl.Launcher.main . public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.hmcl_update_source.override", System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link")); - public static final String RESOURCE_UPDATE_URL = System.getProperty("hmcl.resource_update_source.override", "https://github.com/huanghongxun/HMCL/raw/javafx/data-json/dynamic-remote-resources.json"); + public static final String RESOURCE_UPDATE_URL = System.getProperty("hmcl.resource_update_source.override", "https://hmcl.huangyuhui.net/api/dynamic_remote_resource/update_link"); public static final String CONTACT_URL = "https://docs.hmcl.net/help.html"; public static final String HELP_URL = "https://docs.hmcl.net"; public static final String CHANGELOG_URL = "https://docs.hmcl.net/changelog/"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java index bf985d3249..ce28ee0c17 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java @@ -29,18 +29,20 @@ import org.jackhuang.hmcl.util.function.ExceptionalSupplier; import org.jackhuang.hmcl.util.io.HttpRequest; import org.jackhuang.hmcl.util.io.IOUtils; +import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; public final class RemoteResourceManager { private RemoteResourceManager() { @@ -50,34 +52,34 @@ private static final class RemoteResource { @SerializedName("sha1") private final String sha1; - @SerializedName("url") - private final String url; + @SerializedName("urls") + private final String[] urls; private transient byte[] data = null; - private RemoteResource(String sha1, String url) { + private RemoteResource(String sha1, String[] urls) { this.sha1 = sha1; - this.url = url; + this.urls = urls; } public String getSha1() { return this.sha1; } - public String getUrl() { - return this.url; + public String[] getUrls() { + return this.urls; } public byte @Nullable [] getData() { return this.data; } - public void download(Path path, Runnable callback) throws IOException { + public void download(Path path, Runnable callback) { if (data != null) { return; } - new FileDownloadTask(new URL(url), path.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", sha1)) + new FileDownloadTask(Arrays.stream(urls).map(NetworkUtils::toURL).collect(Collectors.toList()), path.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", sha1)) .whenComplete(Schedulers.defaultScheduler(), (result, exception) -> { if (exception != null) { data = Files.readAllBytes(path); diff --git a/data-json/dynamic-remote-resources.json b/data-json/dynamic-remote-resources.json index bb0e55c236..da8fd7846d 100644 --- a/data-json/dynamic-remote-resources.json +++ b/data-json/dynamic-remote-resources.json @@ -2,14 +2,20 @@ "translation": { "mod_data": { "1": { - "url": "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", + "urls": [ + "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", + "https://hmcl.huangyuhui.net/api/dynamic_remote_resource/translation/mod_data/1" + ], "local_path": "HMCL/src/main/resources/assets/mod_data.txt", "sha1": "0ae36a65a00b00176358bd6b0d3c8787b3668c23" } }, "modpack_data": { "1": { - "url": "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", + "urls": [ + "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", + "https://hmcl.huangyuhui.net/api/dynamic_remote_resource/translation/modpack_data/1" + ], "local_path": "HMCL/src/main/resources/assets/modpack_data.txt", "sha1": "b0e771db170835e1154da4c21b7417a688836162" } From 375b2c476e2eb39365289015f1522f62d8237521 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Wed, 29 Nov 2023 21:05:42 +0800 Subject: [PATCH 093/104] Modify dynamic remote resource urls. --- data-json/dynamic-remote-resources.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data-json/dynamic-remote-resources.json b/data-json/dynamic-remote-resources.json index da8fd7846d..a765752068 100644 --- a/data-json/dynamic-remote-resources.json +++ b/data-json/dynamic-remote-resources.json @@ -4,7 +4,7 @@ "1": { "urls": [ "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", - "https://hmcl.huangyuhui.net/api/dynamic_remote_resource/translation/mod_data/1" + "https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt" ], "local_path": "HMCL/src/main/resources/assets/mod_data.txt", "sha1": "0ae36a65a00b00176358bd6b0d3c8787b3668c23" @@ -14,7 +14,7 @@ "1": { "urls": [ "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", - "https://hmcl.huangyuhui.net/api/dynamic_remote_resource/translation/modpack_data/1" + "https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt" ], "local_path": "HMCL/src/main/resources/assets/modpack_data.txt", "sha1": "b0e771db170835e1154da4c21b7417a688836162" From 2db603d82df0e9f99676581fdf21ad655995b5b8 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sun, 10 Dec 2023 18:46:36 +0800 Subject: [PATCH 094/104] Optimize codes with StringUtils.DynamicCommonSubsequence. --- .../game/LocalizedRemoteModRepository.java | 39 +++++++------------ .../org/jackhuang/hmcl/util/StringUtils.java | 26 +++++++++++++ 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java index cfdb927330..73ccd0e13a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java @@ -21,6 +21,7 @@ import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.ui.versions.ModTranslations; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; @@ -30,6 +31,7 @@ import java.util.stream.Stream; public abstract class LocalizedRemoteModRepository implements RemoteModRepository { + // Yes, I'm not kidding you. The similarity check is based on these two magic number. :) private static final int CONTAIN_CHINESE_WEIGHT = 10; private static final int INITIAL_CAPACITY = 16; @@ -61,19 +63,14 @@ public SearchResult search(String gameVersion, Category category, int pageOffset } SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder); - Set searchFilterLetters = new HashSet<>(); - for (int i = 0; i < searchFilter.length(); i++) { - searchFilterLetters.add(searchFilter.charAt(i)); - } RemoteMod[] searchResultArray = new RemoteMod[pageSize]; int chineseIndex = 0, englishIndex = searchResultArray.length - 1; - for (Iterator iterator = searchResult.getUnsortedResults().iterator(); iterator.hasNext(); ) { + for (RemoteMod remoteMod : Lang.toIterable(searchResult.getUnsortedResults())) { if (chineseIndex > englishIndex) { throw new IOException("There are too many search results!"); } - RemoteMod remoteMod = iterator.next(); ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) { searchResultArray[chineseIndex++] = remoteMod; @@ -84,36 +81,26 @@ public SearchResult search(String gameVersion, Category category, int pageOffset int totalPages = searchResult.getTotalPages(); searchResult = null; // Release memory + StringUtils.DynamicCommonSubsequence calc = new StringUtils.DynamicCommonSubsequence(16, 16); return new SearchResult(Stream.concat(Arrays.stream(searchResultArray, 0, chineseIndex).map(remoteMod -> { ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug()); if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { return Pair.pair(remoteMod, Integer.MAX_VALUE); } + String chineseRemoteModName = chineseRemoteMod.getName(); - if (searchFilter.length() == 0 || chineseRemoteModName.length() == 0) { + if (searchFilter.isEmpty() || chineseRemoteModName.isEmpty()) { return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length())); } - int[][] lev = new int[searchFilter.length() + 1][chineseRemoteModName.length() + 1]; - for (int i = 0; i < chineseRemoteModName.length() + 1; i++) { - lev[0][i] = i; - } - for (int i = 0; i < searchFilter.length() + 1; i++) { - lev[i][0] = i; - } - for (int i = 1; i < searchFilter.length() + 1; i++) { - for (int j = 1; j < chineseRemoteModName.length() + 1; j++) { - int countByInsert = lev[i][j - 1] + 1; - int countByDel = lev[i - 1][j] + 1; - int countByReplace = searchFilter.charAt(i - 1) == chineseRemoteModName.charAt(j - 1) ? lev[i - 1][j - 1] : lev[i - 1][j - 1] + 1; - lev[i][j] = Math.min(countByInsert, Math.min(countByDel, countByReplace)); + + int weight = calc.calc(searchFilter, chineseRemoteModName); + for (int i = 0;i < searchFilter.length(); i ++) { + if (chineseRemoteModName.indexOf(searchFilter.charAt(i)) >= 0) { + return Pair.pair(remoteMod, weight + CONTAIN_CHINESE_WEIGHT); } } - - return Pair.pair( - remoteMod, - lev[searchFilter.length()][chineseRemoteModName.length()] - (searchFilterLetters.stream().anyMatch(c -> chineseRemoteModName.indexOf(c) >= 0) ? CONTAIN_CHINESE_WEIGHT : 0) - ); - }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), Arrays.stream(searchResultArray, englishIndex + 1, searchResultArray.length)), totalPages); + return Pair.pair(remoteMod, weight); + }).sorted(Comparator.>comparingInt(Pair::getValue).reversed()).map(Pair::getKey), Arrays.stream(searchResultArray, englishIndex + 1, searchResultArray.length)), totalPages); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java index b58d666d8e..f48d9ef9dc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -325,6 +325,32 @@ public static boolean isAlphabeticOrNumber(String str) { return true; } + public static class DynamicCommonSubsequence { + private LongestCommonSubsequence calculator; + + public DynamicCommonSubsequence(int intLengthA, int intLengthB) { + if (intLengthA > intLengthB) { + calculator = new LongestCommonSubsequence(intLengthA, intLengthB); + } else { + calculator = new LongestCommonSubsequence(intLengthB, intLengthA); + } + } + + public int calc(CharSequence a, CharSequence b) { + if (a.length() < b.length()) { + CharSequence t = a; + a = b; + b = t; + } + + if (calculator.maxLengthA < a.length() || calculator.maxLengthB < b.length()) { + calculator = new LongestCommonSubsequence(a.length(), b.length()); + } + + return calculator.calc(a, b); + } + } + /** * Class for computing the longest common subsequence between strings. */ From 6e21c02fdb3812916bba5521cb46f916f5b8dfba Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sun, 10 Dec 2023 20:58:01 +0800 Subject: [PATCH 095/104] Prevent unofficial HMCL to access HMCL Resource Update URL. --- .../resource/RemoteResourceManager.java | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java index ce28ee0c17..43761a22cb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java @@ -19,12 +19,12 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; -import javafx.collections.FXCollections; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.versions.ModTranslations; +import org.jackhuang.hmcl.upgrade.hmcl.IntegrityChecker; import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.function.ExceptionalSupplier; import org.jackhuang.hmcl.util.io.HttpRequest; @@ -39,9 +39,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; public final class RemoteResourceManager { @@ -180,38 +180,26 @@ public int hashCode() { } } - private static final Map>> remoteResources = FXCollections.observableMap(new HashMap<>()); - private static boolean fetching = false; + private static final Map>> remoteResources = new ConcurrentHashMap<>(); - private static final Map keys = new HashMap<>(); + private static final Map keys = new ConcurrentHashMap<>(); public static void init() { - if (remoteResources.size() != 0) { - return; - } - - synchronized (RemoteResourceManager.class) { - if (fetching) { - return; - } - fetching = true; - - Task.>>>supplyAsync(() -> HttpRequest.GET(Metadata.RESOURCE_UPDATE_URL).getJson( - new TypeToken>>>() { - }.getType()) - ).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> { - if (exception == null) { - remoteResources.clear(); - remoteResources.putAll(result); - - for (RemoteResourceKey key : keys.values()) { - key.downloadRemoteResourceIfNecessary(); - } + Task.>>>supplyAsync(() -> + IntegrityChecker.isSelfVerified() ? HttpRequest.GET(Metadata.RESOURCE_UPDATE_URL).getJson( + new TypeToken>>>() { + }.getType() + ) : null + ).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> { + if (exception == null && result != null) { + remoteResources.clear(); + remoteResources.putAll(result); + + for (RemoteResourceKey key : keys.values()) { + key.downloadRemoteResourceIfNecessary(); } - - fetching = false; - }).start(); - } + } + }).start(); } public static void register() { From 284b84abbdcdb9bf144311bed4f86b8c4fa3d6fe Mon Sep 17 00:00:00 2001 From: burningtnt Date: Fri, 15 Dec 2023 17:03:58 +0800 Subject: [PATCH 096/104] Zip the dynamic-remote-resources json by Gradle automatically. --- HMCL/build.gradle.kts | 42 ++++++++++++++++----- data-json/dynamic-remote-resources-raw.json | 24 ++++++++++++ data-json/dynamic-remote-resources.json | 25 +----------- 3 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 data-json/dynamic-remote-resources-raw.json diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 0adfedf5ca..548429412c 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -8,7 +8,6 @@ import java.security.KeyFactory import java.security.MessageDigest import java.security.Signature import java.security.spec.PKCS8EncodedKeySpec -import java.util.Locale import java.util.zip.ZipFile buildscript { @@ -96,18 +95,24 @@ fun attachSignature(jar: File) { } tasks.getByName("compileJava") { - dependsOn(tasks.create("checkDynamicRemoteResourcesHash") { + dependsOn(tasks.create("computeDynamicResources") { + this@create.inputs.file(rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources-raw.json")) + this@create.outputs.file(rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources.json")) + doLast { - Files.newInputStream(rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources.json")) - .use { fis -> - (Gson().fromJson( - String(fis.readAllBytes(), Charsets.UTF_8), - JsonElement::class.java - ) as JsonObject).asMap().forEach { (namespace, namespaceData) -> + Gson().also { gsonInstance -> + Files.newBufferedReader( + rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources-raw.json"), + Charsets.UTF_8 + ).use { br -> + (gsonInstance.fromJson(br, JsonElement::class.java) as JsonObject) + }.also { data -> + data.asMap().forEach { (namespace, namespaceData) -> (namespaceData as JsonObject).asMap().forEach { (name, nameData) -> (nameData as JsonObject).asMap().forEach { (version, versionData) -> require(versionData is JsonObject) - val localPath = (versionData.get("local_path") as com.google.gson.JsonPrimitive).asString + val localPath = + (versionData.get("local_path") as com.google.gson.JsonPrimitive).asString val sha1 = (versionData.get("sha1") as com.google.gson.JsonPrimitive).asString val currentSha1 = digest( @@ -121,7 +126,26 @@ tasks.getByName("compileJava") { } } } + + rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources.json").also { zippedPath -> + gsonInstance.toJson(data).also { expectedData -> + if (Files.exists(zippedPath)) { + Files.readString(zippedPath, Charsets.UTF_8).also { rawData -> + if (!rawData.equals(expectedData)) { + if (System.getenv("GITHUB_SHA") == null) { + Files.writeString(zippedPath, expectedData, Charsets.UTF_8) + } else { + throw IllegalStateException("Mismatched zipped dynamic-remote-resources json file!") + } + } + } + } else { + Files.writeString(zippedPath, expectedData, Charsets.UTF_8) + } + } + } } + } } }) } diff --git a/data-json/dynamic-remote-resources-raw.json b/data-json/dynamic-remote-resources-raw.json new file mode 100644 index 0000000000..a765752068 --- /dev/null +++ b/data-json/dynamic-remote-resources-raw.json @@ -0,0 +1,24 @@ +{ + "translation": { + "mod_data": { + "1": { + "urls": [ + "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", + "https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt" + ], + "local_path": "HMCL/src/main/resources/assets/mod_data.txt", + "sha1": "0ae36a65a00b00176358bd6b0d3c8787b3668c23" + } + }, + "modpack_data": { + "1": { + "urls": [ + "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", + "https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt" + ], + "local_path": "HMCL/src/main/resources/assets/modpack_data.txt", + "sha1": "b0e771db170835e1154da4c21b7417a688836162" + } + } + } +} \ No newline at end of file diff --git a/data-json/dynamic-remote-resources.json b/data-json/dynamic-remote-resources.json index a765752068..b14477adee 100644 --- a/data-json/dynamic-remote-resources.json +++ b/data-json/dynamic-remote-resources.json @@ -1,24 +1 @@ -{ - "translation": { - "mod_data": { - "1": { - "urls": [ - "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", - "https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt" - ], - "local_path": "HMCL/src/main/resources/assets/mod_data.txt", - "sha1": "0ae36a65a00b00176358bd6b0d3c8787b3668c23" - } - }, - "modpack_data": { - "1": { - "urls": [ - "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", - "https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt" - ], - "local_path": "HMCL/src/main/resources/assets/modpack_data.txt", - "sha1": "b0e771db170835e1154da4c21b7417a688836162" - } - } - } -} \ No newline at end of file +{"translation":{"mod_data":{"1":{"urls":["https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt","https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt"],"local_path":"HMCL/src/main/resources/assets/mod_data.txt","sha1":"0ae36a65a00b00176358bd6b0d3c8787b3668c23"}},"modpack_data":{"1":{"urls":["https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt","https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt"],"local_path":"HMCL/src/main/resources/assets/modpack_data.txt","sha1":"b0e771db170835e1154da4c21b7417a688836162"}}}} \ No newline at end of file From c0a2fd525c3eecf4da60dadf256cc973e93b4598 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Fri, 15 Dec 2023 17:06:23 +0800 Subject: [PATCH 097/104] Remove unnecessary getters. --- .../resource/RemoteResourceManager.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java index 43761a22cb..b9c4a31dba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java @@ -62,18 +62,6 @@ private RemoteResource(String sha1, String[] urls) { this.urls = urls; } - public String getSha1() { - return this.sha1; - } - - public String[] getUrls() { - return this.urls; - } - - public byte @Nullable [] getData() { - return this.data; - } - public void download(Path path, Runnable callback) { if (data != null) { return; @@ -134,15 +122,15 @@ public InputStream getResource() throws IOException { return getLocalResource(); } - if (remoteResource.getSha1().equals(getLocalResourceSha1())) { + if (remoteResource.sha1.equals(getLocalResourceSha1())) { return getLocalResource(); } - if (remoteResource.getData() == null) { + if (remoteResource.data == null) { return null; } - return new ByteArrayInputStream(remoteResource.getData()); + return new ByteArrayInputStream(remoteResource.data); } public void downloadRemoteResourceIfNecessary() throws IOException { @@ -152,7 +140,7 @@ public void downloadRemoteResourceIfNecessary() throws IOException { return; } - if (remoteResource.getSha1().equals(getLocalResourceSha1())) { + if (remoteResource.sha1.equals(getLocalResourceSha1())) { return; } From ee1c063ce11209f19a0fbeb28eee507bf4230a4e Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sat, 16 Dec 2023 15:27:03 +0800 Subject: [PATCH 098/104] Fix --- .../main/java/org/jackhuang/hmcl/setting/VersionIconType.java | 2 +- .../java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java | 1 + .../main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java | 2 +- .../src/main/java/org/jackhuang/hmcl/download/MaintainTask.java | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java index 0d8b1595a9..df773199a5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java @@ -26,7 +26,7 @@ public enum VersionIconType { COMMAND("/assets/img/command.png"), CRAFT_TABLE("/assets/img/craft_table.png"), FABRIC("/assets/img/fabric.png"), - FORGE("/assets/img/forge.png"), + FORGE("/assets/img/forge.png"), NEO_FORGED("/assets/img/neoforge.png"), FURNACE("/assets/img/furnace.png"), QUILT("/assets/img/quilt.png"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index 01f26789c0..b0003e2d6c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -65,6 +65,7 @@ public VersionIconDialog(Profile profile, String versionId, Runnable onFinish) { createIcon(VersionIconType.CRAFT_TABLE), createIcon(VersionIconType.FABRIC), createIcon(VersionIconType.FORGE), + createIcon(VersionIconType.NEO_FORGED), createIcon(VersionIconType.FURNACE), createIcon(VersionIconType.QUILT) ); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index aafbae461e..4550e78430 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -187,7 +187,7 @@ public String patchVersion(String libraryVersion) { return super.patchVersion(libraryVersion); } }, - NEO_FORGED(true, "neoforge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("(forge|fmlloader)"), ModLoaderType.NEO_FORGED) { + NEO_FORGED(true, "neoforge", Pattern.compile("net\\.neoforged\\.fancymodloader"), Pattern.compile("(core|loader)"), ModLoaderType.NEO_FORGED) { private final Pattern NEO_FORGE_VERSION_MATCHER = Pattern.compile("^([0-9.]+)-(?[0-9.]+)(-([0-9.]+))?$"); @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java index e8fe8a1e99..7cbcf24fb6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java @@ -208,7 +208,7 @@ private static Version maintainGameWithCpwBoostrapLauncher(GameRepository reposi LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version); VersionLibraryBuilder builder = new VersionLibraryBuilder(version); - if (!libraryAnalyzer.has(FORGE)) return version; + if (!libraryAnalyzer.has(FORGE) && !libraryAnalyzer.has(NEO_FORGED)) return version; Optional bslVersion = libraryAnalyzer.getVersion(BOOTSTRAP_LAUNCHER); From db26fec25e8e4e5520a73515174b1777f74021bf Mon Sep 17 00:00:00 2001 From: burningtnt Date: Mon, 18 Dec 2023 20:20:42 +0800 Subject: [PATCH 099/104] Fix LibraryAnalyzer: Cannot analyze the version number of NeoForge correctly. --- .../jackhuang/hmcl/game/LauncherHelper.java | 4 +- .../hmcl/download/LibraryAnalyzer.java | 71 +++++++++++++------ .../hmcl/game/JavaVersionConstraint.java | 1 - 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 6a44ec838b..9fac1a82cd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -506,9 +506,7 @@ private static Task checkGameState(Profile profile, VersionSetting break; case MODDED_JAVA_16: // Minecraft<=1.17.1+Forge[37.0.0,37.0.60) not compatible with Java 17 - String forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE) - .map(LibraryAnalyzer.LibraryType.FORGE::patchVersion) - .orElse(null); + String forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE).orElse(null); if (forgePatchVersion != null && VersionNumber.VERSION_COMPARATOR.compare(forgePatchVersion, "37.0.60") < 0) suggestions.add(i18n("launch.advice.forge37_0_60")); else diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index 4550e78430..37fe00bb9a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -17,9 +17,7 @@ */ package org.jackhuang.hmcl.download; -import org.jackhuang.hmcl.game.Library; -import org.jackhuang.hmcl.game.Version; -import org.jackhuang.hmcl.game.VersionProvider; +import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.util.Pair; import org.jetbrains.annotations.NotNull; @@ -113,6 +111,7 @@ private Version removingMatchedLibrary(Version version, String libraryId) { /** * Remove library by library id + * * @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"/"quilt"/"neoforged" * @return this */ @@ -139,7 +138,7 @@ public static LibraryAnalyzer analyze(Version version) { for (Library library : version.resolve(null).getLibraries()) { for (LibraryType type : LibraryType.values()) { if (type.matchLibrary(library)) { - libraries.put(type.getPatchId(), pair(library, type.patchVersion(library.getVersion()))); + libraries.put(type.getPatchId(), pair(library, type.patchVersion(version, library.getVersion()))); break; } } @@ -179,25 +178,55 @@ public enum LibraryType { private final Pattern FORGE_VERSION_MATCHER = Pattern.compile("^([0-9.]+)-(?[0-9.]+)(-([0-9.]+))?$"); @Override - public String patchVersion(String libraryVersion) { + public String patchVersion(Version gameVersion, String libraryVersion) { Matcher matcher = FORGE_VERSION_MATCHER.matcher(libraryVersion); if (matcher.find()) { return matcher.group("forge"); } - return super.patchVersion(libraryVersion); + return super.patchVersion(gameVersion, libraryVersion); } }, NEO_FORGED(true, "neoforge", Pattern.compile("net\\.neoforged\\.fancymodloader"), Pattern.compile("(core|loader)"), ModLoaderType.NEO_FORGED) { - private final Pattern NEO_FORGE_VERSION_MATCHER = Pattern.compile("^([0-9.]+)-(?[0-9.]+)(-([0-9.]+))?$"); - @Override - public String patchVersion(String libraryVersion) { - Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion); - if (matcher.find()) { - return matcher.group("neoforged"); + public String patchVersion(Version gameVersion, String libraryVersion) { + String res = scanVersion(gameVersion); + if (res != null) { + return res; + } + + for (Version patch : gameVersion.getPatches()) { + res = scanVersion(patch); + if (res != null) { + return res; + } + } + + return super.patchVersion(gameVersion, libraryVersion); + } + + private String scanVersion(Version version) { + Optional optArgument = version.getArguments(); + if (!optArgument.isPresent()) { + return null; + } + List gameArguments = optArgument.get().getGame(); + if (gameArguments == null) { + return null; } - return super.patchVersion(libraryVersion); + + for (int i = 0; i < gameArguments.size() - 1; i++) { + Argument argument = gameArguments.get(i); + if (argument instanceof StringArgument && "--fml.neoForgeVersion".equals(((StringArgument) argument).getArgument())) { + Argument next = gameArguments.get(i + 1); + if (next instanceof StringArgument) { + return ((StringArgument) next).getArgument(); + } + return null; // Normally, there should not be two --fml.neoForgeVersion argument. + } + } + return null; } + }, LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader"), ModLoaderType.LITE_LOADER), OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$"), null), @@ -241,7 +270,7 @@ public boolean matchLibrary(Library library) { return group.matcher(library.getGroupId()).matches() && artifact.matcher(library.getArtifactId()).matches(); } - public String patchVersion(String libraryVersion) { + public String patchVersion(Version gameVersion, String libraryVersion) { return libraryVersion; } } @@ -271,14 +300,14 @@ public String getLibraryVersion() { public static final String MOD_LAUNCHER_MAIN = "cpw.mods.modlauncher.Launcher"; public static final String BOOTSTRAP_LAUNCHER_MAIN = "cpw.mods.bootstraplauncher.BootstrapLauncher"; - public static final String[] FORGE_TWEAKERS = new String[] { - "net.minecraftforge.legacy._1_5_2.LibraryFixerTweaker", // 1.5.2 - "cpw.mods.fml.common.launcher.FMLTweaker", // 1.6.1 ~ 1.7.10 - "net.minecraftforge.fml.common.launcher.FMLTweaker" // 1.8 ~ 1.12.2 + public static final String[] FORGE_TWEAKERS = new String[]{ + "net.minecraftforge.legacy._1_5_2.LibraryFixerTweaker", // 1.5.2 + "cpw.mods.fml.common.launcher.FMLTweaker", // 1.6.1 ~ 1.7.10 + "net.minecraftforge.fml.common.launcher.FMLTweaker" // 1.8 ~ 1.12.2 }; - public static final String[] OPTIFINE_TWEAKERS = new String[] { - "optifine.OptiFineTweaker", - "optifine.OptiFineForgeTweaker" + public static final String[] OPTIFINE_TWEAKERS = new String[]{ + "optifine.OptiFineTweaker", + "optifine.OptiFineForgeTweaker" }; public static final String LITELOADER_TWEAKER = "com.mumfrey.liteloader.launch.LiteLoaderTweaker"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java index 53ec612428..c8f5a37993 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java @@ -145,7 +145,6 @@ protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullabl @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { if (version == null || javaVersion == null || analyzer == null) return false; VersionNumber forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE) - .map(LibraryAnalyzer.LibraryType.FORGE::patchVersion) .map(VersionNumber::asVersion) .orElse(null); if (forgePatchVersion == null) { From 11405b5641b5a2061d31299f62149e30c26d0ef5 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Mon, 18 Dec 2023 20:45:22 +0800 Subject: [PATCH 100/104] Support to choose the NeoForge icon if a game is installed with NeoForge. --- .../main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 2609468b9b..3644ac5d23 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -279,6 +279,8 @@ else if (LibraryAnalyzer.isModded(this, version)) { return newBuiltinImage("/assets/img/fabric.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE)) return newBuiltinImage("/assets/img/forge.png"); + else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGED)) + return newBuiltinImage("/assets/img/neoforge.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT)) return newBuiltinImage("/assets/img/quilt.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.OPTIFINE)) From a82a8a32467bc6d67364d15bc74d355aebc5b267 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Tue, 2 Jan 2024 08:59:28 +0800 Subject: [PATCH 101/104] Fix checkstyle --- .../main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index 8fee0bf0ad..37fe00bb9a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -19,7 +19,6 @@ import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.mod.ModLoaderType; -import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.util.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; From 2a6093f53f6ea2dae51b52625bcd0b9c2fa47efd Mon Sep 17 00:00:00 2001 From: burningtnt Date: Fri, 5 Jan 2024 15:07:37 +0800 Subject: [PATCH 102/104] Fix: The version number on DownloadPage of NeoForge is incorrect, which has leading string 'neoforge-'. --- .../download/neoforge/NeoForgedBMCLVersionList.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java index 3fde0e6f8a..34b18622fc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java @@ -50,27 +50,32 @@ public boolean hasType() { @Override public CompletableFuture loadAsync() { - throw new UnsupportedOperationException("NeoForgedBMCLVersionList does not support loading the entire Forge remote version list."); + throw new UnsupportedOperationException("NeoForgedBMCLVersionList does not support loading the entire NeoForge remote version list."); } @Override public CompletableFuture refreshAsync() { - throw new UnsupportedOperationException("NeoForgedBMCLVersionList does not support loading the entire Forge remote version list."); + throw new UnsupportedOperationException("NeoForgedBMCLVersionList does not support loading the entire NeoForge remote version list."); } @Override public CompletableFuture refreshAsync(String gameVersion) { return CompletableFuture.completedFuture((Void) null) - .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).>getJson(new TypeToken>(){}.getType()))) + .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).>getJson(new TypeToken>() { + }.getType()))) .thenAcceptAsync(neoForgedVersions -> { lock.writeLock().lock(); try { versions.clear(gameVersion); for (NeoForgedVersion neoForgedVersion : neoForgedVersions) { + String nf = StringUtils.removePrefix( + neoForgedVersion.version, + "1.20.1".equals(gameVersion) ? "1.20.1-forge-" : "neoforge-" // Som of the version numbers for 1.20.1 are like forge. + ); versions.put(gameVersion, new NeoForgedRemoteVersion( neoForgedVersion.mcVersion, - neoForgedVersion.version, + nf, Lang.immutableListOf( apiRoot + "/neoforge/version/" + neoForgedVersion.version + "/download/installer.jar" ) From dbf90995ad0227a45875193bb455075dc6a37e84 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sat, 6 Jan 2024 15:21:36 +0800 Subject: [PATCH 103/104] Fix. --- HMCL/src/main/resources/assets/lang/I18N.properties | 1 + HMCL/src/main/resources/assets/lang/I18N_zh.properties | 1 + HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 1 + .../src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index c114353a3c..e5accb2cb4 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -840,6 +840,7 @@ modrinth.category.misc=Misc modrinth.category.mobs=Mobs modrinth.category.modloader=Modloader modrinth.category.multiplayer=Multiplayer +modrinth.category.neoforge=NeoForge modrinth.category.optimization=Optimization modrinth.category.paper=Paper modrinth.category.purpur=Purpur diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 4341b46c6d..ae0c66ad93 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -709,6 +709,7 @@ modrinth.category.misc=其他 modrinth.category.mobs=生物 modrinth.category.modloader=Modloader modrinth.category.multiplayer=多人 +modrinth.category.neoforge=NeoForge modrinth.category.optimization=最佳化 modrinth.category.paper=Paper modrinth.category.purpur=Purpur diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 9b0ab9b882..6e1f19f25d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -708,6 +708,7 @@ modrinth.category.misc=其他 modrinth.category.mobs=生物 modrinth.category.modloader=Modloader modrinth.category.multiplayer=多人 +modrinth.category.neoforge=NeoForge modrinth.category.optimization=优化 modrinth.category.paper=Paper modrinth.category.purpur=Purpur diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java index 6448edfcbf..006c8cfbe8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java @@ -588,7 +588,7 @@ public RemoteMod.Version toVersion() { if ("fabric".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FABRIC); else if ("forge".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FORGE); else if ("quilt".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.QUILT); - else if ("neoforge".equals(version)) return Stream.of(ModLoaderType.NEO_FORGED); + else if ("neoforge".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.NEO_FORGED); else return Stream.empty(); }).collect(Collectors.toList()) ); From 75da31773885c9fbbfb281c7d7679d266d21c56a Mon Sep 17 00:00:00 2001 From: burningtnt Date: Mon, 8 Jan 2024 10:05:03 +0800 Subject: [PATCH 104/104] Rename references. Fix: Invalid spaces in VersionIconType. --- .../hmcl/game/HMCLGameRepository.java | 2 +- .../hmcl/setting/VersionIconType.java | 4 +-- .../org/jackhuang/hmcl/ui/InstallerItem.java | 26 +++++++------- .../hmcl/ui/construct/TaskListPane.java | 4 +-- .../hmcl/ui/download/VersionsPage.java | 6 ++-- .../hmcl/ui/versions/InstallerListPage.java | 2 +- .../hmcl/ui/versions/VersionIconDialog.java | 2 +- .../download/BMCLAPIDownloadProvider.java | 8 ++--- .../download/DefaultDependencyManager.java | 4 +-- .../hmcl/download/LibraryAnalyzer.java | 4 +-- .../jackhuang/hmcl/download/MaintainTask.java | 2 +- .../hmcl/download/MojangDownloadProvider.java | 8 ++--- ...List.java => NeoForgeBMCLVersionList.java} | 34 +++++++++---------- ...tallTask.java => NeoForgeInstallTask.java} | 22 ++++++------ ...lTask.java => NeoForgeOldInstallTask.java} | 6 ++-- ...ersion.java => NeoForgeRemoteVersion.java} | 8 ++--- .../hmcl/mod/curse/CurseInstallTask.java | 2 +- 17 files changed, 72 insertions(+), 72 deletions(-) rename HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/{NeoForgedBMCLVersionList.java => NeoForgeBMCLVersionList.java} (72%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/{NeoForgedInstallTask.java => NeoForgeInstallTask.java} (79%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/{NeoForgedOldInstallTask.java => NeoForgeOldInstallTask.java} (98%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/{NeoForgedRemoteVersion.java => NeoForgeRemoteVersion.java} (56%) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 3644ac5d23..2faa26e382 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -279,7 +279,7 @@ else if (LibraryAnalyzer.isModded(this, version)) { return newBuiltinImage("/assets/img/fabric.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE)) return newBuiltinImage("/assets/img/forge.png"); - else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGED)) + else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE)) return newBuiltinImage("/assets/img/neoforge.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT)) return newBuiltinImage("/assets/img/quilt.png"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java index df773199a5..1a9c418691 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java @@ -26,8 +26,8 @@ public enum VersionIconType { COMMAND("/assets/img/command.png"), CRAFT_TABLE("/assets/img/craft_table.png"), FABRIC("/assets/img/fabric.png"), - FORGE("/assets/img/forge.png"), - NEO_FORGED("/assets/img/neoforge.png"), + FORGE("/assets/img/forge.png"), + NEO_FORGE("/assets/img/neoforge.png"), FURNACE("/assets/img/furnace.png"), QUILT("/assets/img/quilt.png"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index b72f07487f..7bc6a2b82e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -125,7 +125,7 @@ public static class InstallerItemGroup { public final InstallerItem fabric = new InstallerItem(FABRIC); public final InstallerItem fabricApi = new InstallerItem(FABRIC_API); public final InstallerItem forge = new InstallerItem(FORGE); - public final InstallerItem neoForged = new InstallerItem(NEO_FORGED); + public final InstallerItem neoForge = new InstallerItem(NEO_FORGE); public final InstallerItem liteLoader = new InstallerItem(LITELOADER); public final InstallerItem optiFine = new InstallerItem(OPTIFINE); public final InstallerItem quilt = new InstallerItem(QUILT); @@ -135,11 +135,11 @@ public InstallerItemGroup() { forge.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); - if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion, neoForged.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, neoForge.libraryVersion)); - neoForged.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { + neoForge.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); @@ -149,27 +149,27 @@ public InstallerItemGroup() { liteLoader.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); - if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion, neoForged.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, neoForge.libraryVersion)); optiFine.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); - if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion, neoForged.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, neoForge.libraryVersion)); for (InstallerItem fabric : new InstallerItem[]{fabric, fabricApi}) { fabric.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); - if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); if (liteLoader.libraryVersion.get() != null) return LITELOADER.getPatchId(); if (optiFine.libraryVersion.get() != null) return OPTIFINE.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); if (quiltApi.libraryVersion.get() != null) return QUILT_API.getPatchId(); return null; - }, forge.libraryVersion, neoForged.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion, quilt.libraryVersion, quiltApi.libraryVersion)); + }, forge.libraryVersion, neoForge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion, quilt.libraryVersion, quiltApi.libraryVersion)); } fabricApi.dependencyName.bind(Bindings.createStringBinding(() -> { @@ -182,11 +182,11 @@ public InstallerItemGroup() { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (fabricApi.libraryVersion.get() != null) return FABRIC_API.getPatchId(); if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); - if (neoForged.libraryVersion.get() != null) return NEO_FORGED.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); if (liteLoader.libraryVersion.get() != null) return LITELOADER.getPatchId(); if (optiFine.libraryVersion.get() != null) return OPTIFINE.getPatchId(); return null; - }, fabric.libraryVersion, fabricApi.libraryVersion, forge.libraryVersion, neoForged.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion)); + }, fabric.libraryVersion, fabricApi.libraryVersion, forge.libraryVersion, neoForge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion)); } quiltApi.dependencyName.bind(Bindings.createStringBinding(() -> { @@ -196,7 +196,7 @@ public InstallerItemGroup() { } public InstallerItem[] getLibraries() { - return new InstallerItem[]{game, forge, neoForged, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; + return new InstallerItem[]{game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index bcfbffd405..ffd02c349a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -36,7 +36,7 @@ import org.jackhuang.hmcl.download.game.GameInstallTask; import org.jackhuang.hmcl.download.java.JavaDownloadTask; import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask; -import org.jackhuang.hmcl.download.neoforge.NeoForgedInstallTask; +import org.jackhuang.hmcl.download.neoforge.NeoForgeInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.quilt.QuiltAPIInstallTask; import org.jackhuang.hmcl.download.quilt.QuiltInstallTask; @@ -124,7 +124,7 @@ public void onRunning(Task task) { task.setName(i18n("install.installer.install", i18n("install.installer.game"))); } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.forge"))); - } else if (task instanceof NeoForgedInstallTask) { + } else if (task instanceof NeoForgeInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.neoforge"))); } else if (task instanceof LiteLoaderInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.liteloader"))); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index b6fd3825db..e5b8311f6b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -36,7 +36,7 @@ import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion; import org.jackhuang.hmcl.download.game.GameRemoteVersion; import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion; -import org.jackhuang.hmcl.download.neoforge.NeoForgedRemoteVersion; +import org.jackhuang.hmcl.download.neoforge.NeoForgeRemoteVersion; import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion; @@ -336,8 +336,8 @@ else if (remoteVersion instanceof OptiFineRemoteVersion) iconType = VersionIconType.COMMAND; else if (remoteVersion instanceof ForgeRemoteVersion) iconType = VersionIconType.FORGE; - else if (remoteVersion instanceof NeoForgedRemoteVersion) - iconType = VersionIconType.NEO_FORGED; + else if (remoteVersion instanceof NeoForgeRemoteVersion) + iconType = VersionIconType.NEO_FORGE; else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion) iconType = VersionIconType.FABRIC; else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof QuiltAPIRemoteVersion) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index 8428dbfad4..b5ebc9865f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -86,7 +86,7 @@ public void loadVersion(Profile profile, String versionId) { InstallerItem.InstallerItemGroup group = new InstallerItem.InstallerItemGroup(); - // Conventional libraries: game, fabric, forge, neoforged, liteloader, optifine + // Conventional libraries: game, fabric, forge, neoforge, liteloader, optifine for (InstallerItem installerItem : group.getLibraries()) { String libraryId = installerItem.getLibraryId(); String libraryVersion = analyzer.getVersion(libraryId).orElse(null); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index b0003e2d6c..cde2d27f25 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -65,7 +65,7 @@ public VersionIconDialog(Profile profile, String versionId, Runnable onFinish) { createIcon(VersionIconType.CRAFT_TABLE), createIcon(VersionIconType.FABRIC), createIcon(VersionIconType.FORGE), - createIcon(VersionIconType.NEO_FORGED), + createIcon(VersionIconType.NEO_FORGE), createIcon(VersionIconType.FURNACE), createIcon(VersionIconType.QUILT) ); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java index b342c9cd42..bb7105900f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -22,7 +22,7 @@ import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList; -import org.jackhuang.hmcl.download.neoforge.NeoForgedBMCLVersionList; +import org.jackhuang.hmcl.download.neoforge.NeoForgeBMCLVersionList; import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; import org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList; import org.jackhuang.hmcl.download.quilt.QuiltVersionList; @@ -37,7 +37,7 @@ public class BMCLAPIDownloadProvider implements DownloadProvider { private final FabricVersionList fabric; private final FabricAPIVersionList fabricApi; private final ForgeBMCLVersionList forge; - private final NeoForgedBMCLVersionList neoforged; + private final NeoForgeBMCLVersionList neoforge; private final LiteLoaderBMCLVersionList liteLoader; private final OptiFineBMCLVersionList optifine; private final QuiltVersionList quilt; @@ -49,7 +49,7 @@ public BMCLAPIDownloadProvider(String apiRoot) { this.fabric = new FabricVersionList(this); this.fabricApi = new FabricAPIVersionList(this); this.forge = new ForgeBMCLVersionList(apiRoot); - this.neoforged = new NeoForgedBMCLVersionList(apiRoot); + this.neoforge = new NeoForgeBMCLVersionList(apiRoot); this.liteLoader = new LiteLoaderBMCLVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); this.quilt = new QuiltVersionList(this); @@ -82,7 +82,7 @@ public VersionList getVersionListById(String id) { case "forge": return forge; case "neoforge": - return neoforged; + return neoforge; case "liteloader": return liteLoader; case "optifine": diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java index 5770848440..559f6b70fd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -21,7 +21,7 @@ import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.download.game.GameLibrariesTask; -import org.jackhuang.hmcl.download.neoforge.NeoForgedInstallTask; +import org.jackhuang.hmcl.download.neoforge.NeoForgeInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.game.Artifact; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -181,7 +181,7 @@ public Task installLibraryAsync(Version oldVersion, Path installer) { return Task .composeAsync(() -> { try { - return NeoForgedInstallTask.install(this, oldVersion, installer); + return NeoForgeInstallTask.install(this, oldVersion, installer); } catch (IOException ignore) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index 37fe00bb9a..1d664484a1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -112,7 +112,7 @@ private Version removingMatchedLibrary(Version version, String libraryId) { /** * Remove library by library id * - * @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"/"quilt"/"neoforged" + * @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"/"quilt"/"neoforge" * @return this */ public LibraryAnalyzer removeLibrary(String libraryId) { @@ -186,7 +186,7 @@ public String patchVersion(Version gameVersion, String libraryVersion) { return super.patchVersion(gameVersion, libraryVersion); } }, - NEO_FORGED(true, "neoforge", Pattern.compile("net\\.neoforged\\.fancymodloader"), Pattern.compile("(core|loader)"), ModLoaderType.NEO_FORGED) { + NEO_FORGE(true, "neoforge", Pattern.compile("net\\.neoforged\\.fancymodloader"), Pattern.compile("(core|loader)"), ModLoaderType.NEO_FORGED) { @Override public String patchVersion(Version gameVersion, String libraryVersion) { String res = scanVersion(gameVersion); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java index 7cbcf24fb6..ced364ea55 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java @@ -208,7 +208,7 @@ private static Version maintainGameWithCpwBoostrapLauncher(GameRepository reposi LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version); VersionLibraryBuilder builder = new VersionLibraryBuilder(version); - if (!libraryAnalyzer.has(FORGE) && !libraryAnalyzer.has(NEO_FORGED)) return version; + if (!libraryAnalyzer.has(FORGE) && !libraryAnalyzer.has(NEO_FORGE)) return version; Optional bslVersion = libraryAnalyzer.getVersion(BOOTSTRAP_LAUNCHER); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java index 23e8307caf..33d11327e8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -22,7 +22,7 @@ import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList; -import org.jackhuang.hmcl.download.neoforge.NeoForgedBMCLVersionList; +import org.jackhuang.hmcl.download.neoforge.NeoForgeBMCLVersionList; import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; import org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList; import org.jackhuang.hmcl.download.quilt.QuiltVersionList; @@ -36,7 +36,7 @@ public class MojangDownloadProvider implements DownloadProvider { private final FabricVersionList fabric; private final FabricAPIVersionList fabricApi; private final ForgeBMCLVersionList forge; - private final NeoForgedBMCLVersionList neoforged; + private final NeoForgeBMCLVersionList neoforge; private final LiteLoaderVersionList liteLoader; private final OptiFineBMCLVersionList optifine; private final QuiltVersionList quilt; @@ -49,7 +49,7 @@ public MojangDownloadProvider() { this.fabric = new FabricVersionList(this); this.fabricApi = new FabricAPIVersionList(this); this.forge = new ForgeBMCLVersionList(apiRoot); - this.neoforged = new NeoForgedBMCLVersionList(apiRoot); + this.neoforge = new NeoForgeBMCLVersionList(apiRoot); this.liteLoader = new LiteLoaderVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); this.quilt = new QuiltVersionList(this); @@ -78,7 +78,7 @@ public VersionList getVersionListById(String id) { case "forge": return forge; case "neoforge": - return neoforged; + return neoforge; case "liteloader": return liteLoader; case "optifine": diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java similarity index 72% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java index 34b18622fc..cf4cee35b8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java @@ -33,13 +33,13 @@ import static org.jackhuang.hmcl.util.Lang.wrap; -public final class NeoForgedBMCLVersionList extends VersionList { +public final class NeoForgeBMCLVersionList extends VersionList { private final String apiRoot; /** * @param apiRoot API Root of BMCLAPI implementations */ - public NeoForgedBMCLVersionList(String apiRoot) { + public NeoForgeBMCLVersionList(String apiRoot) { this.apiRoot = apiRoot; } @@ -50,34 +50,34 @@ public boolean hasType() { @Override public CompletableFuture loadAsync() { - throw new UnsupportedOperationException("NeoForgedBMCLVersionList does not support loading the entire NeoForge remote version list."); + throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list."); } @Override public CompletableFuture refreshAsync() { - throw new UnsupportedOperationException("NeoForgedBMCLVersionList does not support loading the entire NeoForge remote version list."); + throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list."); } @Override public CompletableFuture refreshAsync(String gameVersion) { return CompletableFuture.completedFuture((Void) null) - .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).>getJson(new TypeToken>() { + .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).>getJson(new TypeToken>() { }.getType()))) - .thenAcceptAsync(neoForgedVersions -> { + .thenAcceptAsync(neoForgeVersions -> { lock.writeLock().lock(); try { versions.clear(gameVersion); - for (NeoForgedVersion neoForgedVersion : neoForgedVersions) { + for (NeoForgeVersion neoForgeVersion : neoForgeVersions) { String nf = StringUtils.removePrefix( - neoForgedVersion.version, + neoForgeVersion.version, "1.20.1".equals(gameVersion) ? "1.20.1-forge-" : "neoforge-" // Som of the version numbers for 1.20.1 are like forge. ); - versions.put(gameVersion, new NeoForgedRemoteVersion( - neoForgedVersion.mcVersion, + versions.put(gameVersion, new NeoForgeRemoteVersion( + neoForgeVersion.mcVersion, nf, Lang.immutableListOf( - apiRoot + "/neoforge/version/" + neoForgedVersion.version + "/download/installer.jar" + apiRoot + "/neoforge/version/" + neoForgeVersion.version + "/download/installer.jar" ) )); } @@ -88,13 +88,13 @@ public CompletableFuture refreshAsync(String gameVersion) { } @Override - public Optional getVersion(String gameVersion, String remoteVersion) { + public Optional getVersion(String gameVersion, String remoteVersion) { remoteVersion = StringUtils.substringAfter(remoteVersion, "-", remoteVersion); return super.getVersion(gameVersion, remoteVersion); } @Immutable - private static final class NeoForgedVersion implements Validation { + private static final class NeoForgeVersion implements Validation { private final String rawVersion; private final String version; @@ -102,7 +102,7 @@ private static final class NeoForgedVersion implements Validation { @SerializedName("mcversion") private final String mcVersion; - public NeoForgedVersion(String rawVersion, String version, String mcVersion) { + public NeoForgeVersion(String rawVersion, String version, String mcVersion) { this.rawVersion = rawVersion; this.version = version; this.mcVersion = mcVersion; @@ -123,13 +123,13 @@ public String getMcVersion() { @Override public void validate() throws JsonParseException { if (this.rawVersion == null) { - throw new JsonParseException("NeoForgedVersion rawVersion cannot be null."); + throw new JsonParseException("NeoForgeVersion rawVersion cannot be null."); } if (this.version == null) { - throw new JsonParseException("NeoForgedVersion version cannot be null."); + throw new JsonParseException("NeoForgeVersion version cannot be null."); } if (this.mcVersion == null) { - throw new JsonParseException("NeoForgedVersion mcversion cannot be null."); + throw new JsonParseException("NeoForgeVersion mcversion cannot be null."); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java similarity index 79% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java index 7384dab44f..58305192b5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java @@ -20,12 +20,12 @@ import static org.jackhuang.hmcl.util.StringUtils.removePrefix; import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; -public final class NeoForgedInstallTask extends Task { +public final class NeoForgeInstallTask extends Task { private final DefaultDependencyManager dependencyManager; private final Version version; - private final NeoForgedRemoteVersion remoteVersion; + private final NeoForgeRemoteVersion remoteVersion; private Path installer = null; @@ -33,7 +33,7 @@ public final class NeoForgedInstallTask extends Task { private Task dependency; - public NeoForgedInstallTask(DefaultDependencyManager dependencyManager, Version version, NeoForgedRemoteVersion remoteVersion) { + public NeoForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, NeoForgeRemoteVersion remoteVersion) { this.dependencyManager = dependencyManager; this.version = version; this.remoteVersion = remoteVersion; @@ -46,7 +46,7 @@ public boolean doPreExecute() { @Override public void preExecute() throws Exception { - installer = Files.createTempFile("neoforged-installer", ".jar"); + installer = Files.createTempFile("neoforge-installer", ".jar"); dependent = new FileDownloadTask( dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()), @@ -93,28 +93,28 @@ public static Task install(DefaultDependencyManager dependencyManager, ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); if (!gameVersion.get().equals(profile.getMinecraft())) throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get()); - return new ForgeNewInstallTask(dependencyManager, version, modifyNeoForgedOldVersion(gameVersion.get(), profile.getVersion()), installer).thenApplyAsync(neoForgeVersion -> { + return new ForgeNewInstallTask(dependencyManager, version, modifyNeoForgeOldVersion(gameVersion.get(), profile.getVersion()), installer).thenApplyAsync(neoForgeVersion -> { if (!neoForgeVersion.getId().equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) || neoForgeVersion.getVersion() == null) { - throw new IOException("Invalid neoforged version."); + throw new IOException("Invalid neoforge version."); } - return neoForgeVersion.setId(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId()).setVersion(neoForgeVersion.getVersion().replace(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId())); + return neoForgeVersion.setId(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId()).setVersion(neoForgeVersion.getVersion().replace(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId())); }); - } else if (LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId().equals(installProfile.get("profile"))) { + } else if (LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId().equals(installProfile.get("profile"))) { ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); if (!gameVersion.get().equals(profile.getMinecraft())) throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get()); - return new NeoForgedOldInstallTask(dependencyManager, version, modifyNeoForgedNewVersion(profile.getVersion()), installer); + return new NeoForgeOldInstallTask(dependencyManager, version, modifyNeoForgeNewVersion(profile.getVersion()), installer); } else { throw new IOException(); } } } - private static String modifyNeoForgedOldVersion(String gameVersion, String version) { + private static String modifyNeoForgeOldVersion(String gameVersion, String version) { return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"); } - private static String modifyNeoForgedNewVersion(String version) { + private static String modifyNeoForgeNewVersion(String version) { return removePrefix(version.replace("neoforge", ""), "-"); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java similarity index 98% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedOldInstallTask.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java index eccbc74a6e..3de56d9dfa 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java @@ -59,7 +59,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; -public class NeoForgedOldInstallTask extends Task { +public class NeoForgeOldInstallTask extends Task { private class ProcessorTask extends Task { @@ -188,7 +188,7 @@ public void execute() throws Exception { private Path tempDir; private AtomicInteger processorDoneCount = new AtomicInteger(0); - NeoForgedOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) { + NeoForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) { this.dependencyManager = dependencyManager; this.gameRepository = dependencyManager.getGameRepository(); this.version = version; @@ -408,7 +408,7 @@ public void execute() throws Exception { setResult(neoForgeVersion .setPriority(30000) - .setId(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId()) + .setId(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId()) .setVersion(selfVersion)); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeRemoteVersion.java similarity index 56% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeRemoteVersion.java index 702f3fa2bb..3a08acedce 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgedRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeRemoteVersion.java @@ -8,13 +8,13 @@ import java.util.List; -public class NeoForgedRemoteVersion extends RemoteVersion { - public NeoForgedRemoteVersion(String gameVersion, String selfVersion, List urls) { - super(LibraryAnalyzer.LibraryType.NEO_FORGED.getPatchId(), gameVersion, selfVersion, null, urls); +public class NeoForgeRemoteVersion extends RemoteVersion { + public NeoForgeRemoteVersion(String gameVersion, String selfVersion, List urls) { + super(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId(), gameVersion, selfVersion, null, urls); } @Override public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { - return new NeoForgedInstallTask(dependencyManager, baseVersion, this); + return new NeoForgeInstallTask(dependencyManager, baseVersion, this); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java index 2042187bf6..6796cf3308 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java @@ -82,7 +82,7 @@ public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile } else if (modLoader.getId().startsWith("fabric-")) { builder.version("fabric", modLoader.getId().substring("fabric-".length())); } else if (modLoader.getId().startsWith("neoforge-")) { - builder.version("neoforged", modLoader.getId().substring("neoforge-".length())); + builder.version("neoforge", modLoader.getId().substring("neoforge-".length())); } } dependents.add(builder.buildAsync());