From 865821379f6fd8c4366d54b7971ef441c8a34600 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 4 Jul 2026 14:00:28 +0800 Subject: [PATCH 01/36] fix ReShaded download --- .../hmcl/ui/download/DownloadPage.java | 16 ++---- .../hmcl/ui/versions/DataPackListPage.java | 2 +- .../ui/versions/DataPackListPageSkin.java | 2 +- .../hmcl/ui/versions/DownloadListPage.java | 2 +- .../hmcl/ui/versions/DownloadPage.java | 52 +++++++++++++------ .../HMCLLocalizedDownloadListPage.java | 28 +++++----- .../hmcl/ui/versions/ModListPageSkin.java | 4 +- .../ui/versions/ResourcePackListPage.java | 10 ++-- .../resources/assets/lang/I18N.properties | 2 +- .../resources/assets/lang/I18N_ar.properties | 2 +- .../resources/assets/lang/I18N_es.properties | 2 +- .../resources/assets/lang/I18N_lzh.properties | 2 +- .../resources/assets/lang/I18N_ru.properties | 2 +- .../resources/assets/lang/I18N_uk.properties | 2 +- .../resources/assets/lang/I18N_zh.properties | 2 +- .../assets/lang/I18N_zh_CN.properties | 2 +- .../jackhuang/hmcl/addon/mod/ModManager.java | 2 +- .../{ => mod}/meta/FabricModMetadata.java | 2 +- .../{ => mod}/meta/ForgeNewModMetadata.java | 2 +- .../{ => mod}/meta/ForgeOldModMetadata.java | 2 +- .../meta/ForgeOldModMetadataLst.java | 2 +- .../addon/{ => mod}/meta/LiteModMetadata.java | 2 +- .../{ => mod}/meta/QuiltModMetadata.java | 2 +- .../hmcl/addon/{meta => pack}/PackMcMeta.java | 2 +- .../addon/{ => pack}/datapack/DataPack.java | 4 +- .../resourcepack/ResourcePackFile.java | 4 +- .../resourcepack/ResourcePackFolder.java | 4 +- .../resourcepack/ResourcePackManager.java | 4 +- .../resourcepack/ResourcePackZipFile.java | 4 +- .../resourcepack/ResourcePackManagerTest.java | 4 +- 30 files changed, 92 insertions(+), 80 deletions(-) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => mod}/meta/FabricModMetadata.java (98%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => mod}/meta/ForgeNewModMetadata.java (99%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => mod}/meta/ForgeOldModMetadata.java (99%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => mod}/meta/ForgeOldModMetadataLst.java (95%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => mod}/meta/LiteModMetadata.java (98%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => mod}/meta/QuiltModMetadata.java (98%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{meta => pack}/PackMcMeta.java (99%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => pack}/datapack/DataPack.java (99%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => pack}/resourcepack/ResourcePackFile.java (97%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => pack}/resourcepack/ResourcePackFolder.java (96%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => pack}/resourcepack/ResourcePackManager.java (99%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/{ => pack}/resourcepack/ResourcePackZipFile.java (97%) 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 58a3cc5fc3a..3315589a9d0 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 @@ -68,12 +68,6 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage { - public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_MOD = - (downloadProvider, repository, version, mod, file) -> download(downloadProvider, repository, version, file, "mods"); - public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_RESOURCE_PACK = - (downloadProvider, repository, version, pack, file) -> download(downloadProvider, repository, version, file, "resourcepacks"); - public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_SHADER = - (downloadProvider, repository, version, shader, file) -> download(downloadProvider, repository, version, file, "shaderpacks"); private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1)); private final TabHeader tab; @@ -92,12 +86,12 @@ public DownloadPage() { this(null); } - public DownloadPage(String uploadVersion) { + public DownloadPage(String updateVersion) { newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(), "game", versionPageNavigator::onGameSelected))); modpackTab.setNodeSupplier(loadVersionFor(() -> { DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((downloadProvider, repository, __, modpack, file) -> { - Versions.downloadModpackImpl(downloadProvider, repository, uploadVersion, modpack, file); + Versions.downloadModpackImpl(downloadProvider, repository, updateVersion, modpack, file); }, false); JFXButton installLocalModpackButton = FXUtils.newRaisedButton(i18n("install.modpack")); @@ -106,9 +100,9 @@ public DownloadPage(String uploadVersion) { page.getActions().add(installLocalModpackButton); return page; })); - modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod(FOR_MOD, true))); - resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack(FOR_RESOURCE_PACK, true))); - shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack(FOR_SHADER, true))); + modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod(true))); + resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack(true))); + shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack(true))); worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteAddonRepository.WORLDS))); tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPage.java index 76d130b17a0..6ae93e36a82 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPage.java @@ -22,7 +22,7 @@ import javafx.scene.control.Skin; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.World; -import org.jackhuang.hmcl.addon.datapack.DataPack; +import org.jackhuang.hmcl.addon.pack.datapack.DataPack; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java index 40fc80daf59..0ca88000fb5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java @@ -43,7 +43,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.util.Duration; -import org.jackhuang.hmcl.addon.datapack.DataPack; +import org.jackhuang.hmcl.addon.pack.datapack.DataPack; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; 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 bd1f09f6973..2129f9cd75a 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 @@ -78,7 +78,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); private final ObservableList versions = FXCollections.observableArrayList(); private final StringProperty selectedVersion = new SimpleStringProperty(); - private final DownloadPage.DownloadCallback callback; + private final DownloadPage.DownloadCallback callback; // ONLY FOR MODPACKS private boolean searchInitialized = false; protected final BooleanProperty supportChinese = new SimpleBooleanProperty(); private final ObservableList actions = FXCollections.observableArrayList(); 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 fb06c3ca0fe..a419bd63ae7 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 @@ -59,6 +59,25 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class DownloadPage extends Control implements DecoratorPage { + public static final DownloadCallback FOR_MOD = + (downloadProvider, repository, version, mod, file) -> + org.jackhuang.hmcl.ui.download.DownloadPage.download(downloadProvider, repository, version, file, "mods"); + public static final DownloadCallback FOR_RESOURCE_PACK = + (downloadProvider, repository, version, pack, file) -> + org.jackhuang.hmcl.ui.download.DownloadPage.download(downloadProvider, repository, version, file, "resourcepacks"); + public static final DownloadCallback FOR_SHADER = + (downloadProvider, repository, version, shader, file) -> + org.jackhuang.hmcl.ui.download.DownloadPage.download(downloadProvider, repository, version, file, "shaderpacks"); + + public static @Nullable DownloadCallback getDownloadCallbackFor(RemoteAddonRepository.Type repoType) { + return switch (repoType) { + case MOD -> FOR_MOD; + case RESOURCE_PACK -> FOR_RESOURCE_PACK; + case SHADER_PACK -> FOR_SHADER; + default -> null; + }; + } + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); private final BooleanProperty loaded = new SimpleBooleanProperty(false); private final BooleanProperty loading = new SimpleBooleanProperty(false); @@ -68,7 +87,7 @@ public class DownloadPage extends Control implements DecoratorPage { private final RemoteAddon addon; private final ModTranslations.Mod mod; private final HMCLGameRepository.InstanceReference instanceReference; - private final DownloadCallback callback; + private final DownloadCallback callback; // ONLY FOR MODPACKS private final DownloadListPage page; private final RemoteAddonRepository.Type type; @@ -156,11 +175,17 @@ public void setFailed(boolean failed) { this.failed.set(failed); } + public @Nullable DownloadCallback getCallbackFor(RemoteAddon addon) { + if (addon.repoType() == RemoteAddonRepository.Type.MODPACK) return this.callback; + return getDownloadCallbackFor(addon.repoType()); + } + public void download(RemoteAddon addon, RemoteAddon.Version file) { - if (this.callback == null) { + var callback = getCallbackFor(addon); + if (callback == null) { saveAs(file); } else { - this.callback.download(page.getDownloadProvider(), instanceReference.repository(), instanceReference.instanceId(), addon, file); + callback.download(page.getDownloadProvider(), instanceReference.repository(), instanceReference.instanceId(), addon, file); } } @@ -386,21 +411,14 @@ private static final class DependencyAddonItem extends LineButton { pane.getChildren().setAll(imageView, content); FXUtils.setLimitHeight(this, 60); - RemoteAddonRepository.Type type = addon.repoType(); - DownloadCallback callback = switch (type) { - case MOD -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_MOD; - case RESOURCE_PACK -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_RESOURCE_PACK; - case SHADER_PACK -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_SHADER; - default -> null; // Dependencies should not be modpacks, worlds or customized stuff - }; setOnAction((e) -> { fireEvent(new DialogCloseEvent()); - Controllers.navigate(new DownloadPage(page, addon, instanceReference, callback)); + Controllers.navigate(new DownloadPage(page, addon, instanceReference, null)); }); setNode(IDX_LEADING, pane); if (addon != RemoteAddon.BROKEN) { - ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(type).getModByCurseForgeId(addon.slug()); + ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(addon.repoType()).getModByCurseForgeId(addon.slug()); content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.title()); content.setSubtitle(addon.description()); for (String category : addon.categories()) { @@ -491,7 +509,7 @@ private static final class AddonItem extends StackPane { } private static final class AddonVersion extends JFXDialogLayout { - public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage selfPage) { + public AddonVersion(RemoteAddon addon, RemoteAddon.Version version, DownloadPage selfPage) { RemoteAddonRepository.Type type = selfPage.type; String title = switch (type) { @@ -505,7 +523,7 @@ public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage s VBox box = new VBox(8); box.setPadding(new Insets(8)); - AddonItem addonItem = new AddonItem(mod, version, selfPage); + AddonItem addonItem = new AddonItem(addon, version, selfPage); addonItem.setMouseTransparent(true); // Item is displayed for info, clicking shouldn't open the dialog again box.getChildren().setAll(addonItem); SpinnerPane spinnerPane = new SpinnerPane(); @@ -527,14 +545,14 @@ public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage s this.setBody(box); JFXButton downloadButton = null; - if (selfPage.callback != null) { - downloadButton = new JFXButton(type == RemoteAddonRepository.Type.MODPACK ? i18n("install.modpack") : i18n("mods.install")); + if (selfPage.getCallbackFor(addon) != null) { + downloadButton = new JFXButton(type == RemoteAddonRepository.Type.MODPACK ? i18n("install.modpack") : i18n("addon.install")); downloadButton.getStyleClass().add("dialog-accept"); downloadButton.setOnAction(e -> { if (type == RemoteAddonRepository.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) { fireEvent(new DialogCloseEvent()); } - selfPage.download(mod, version); + selfPage.download(addon, version); }); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java index 2c1aa9b61f8..bc52cef4cf1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java @@ -30,36 +30,36 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class HMCLLocalizedDownloadListPage extends DownloadListPage { - public static DownloadListPage ofMod(DownloadPage.DownloadCallback callback, boolean versionSelection) { - return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteAddonRepository.Type.MOD, CurseForgeRemoteAddonRepository.MODS, ModrinthRemoteAddonRepository.MODS); + public static DownloadListPage ofMod(boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.MOD, CurseForgeRemoteAddonRepository.MODS, ModrinthRemoteAddonRepository.MODS); } - public static DownloadListPage ofCurseForgeMod(DownloadPage.DownloadCallback callback, boolean versionSelection) { - return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteAddonRepository.Type.MOD, CurseForgeRemoteAddonRepository.MODS, null); + public static DownloadListPage ofCurseForgeMod(boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.MOD, CurseForgeRemoteAddonRepository.MODS, null); } - public static DownloadListPage ofModrinthMod(DownloadPage.DownloadCallback callback, boolean versionSelection) { - return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteAddonRepository.Type.MOD, null, ModrinthRemoteAddonRepository.MODS); + public static DownloadListPage ofModrinthMod(boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.MOD, null, ModrinthRemoteAddonRepository.MODS); } public static DownloadListPage ofModPack(DownloadPage.DownloadCallback callback, boolean versionSelection) { return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteAddonRepository.Type.MODPACK, CurseForgeRemoteAddonRepository.MODPACKS, ModrinthRemoteAddonRepository.MODPACKS); } - public static DownloadListPage ofResourcePack(DownloadPage.DownloadCallback callback, boolean versionSelection) { - return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteAddonRepository.Type.RESOURCE_PACK, CurseForgeRemoteAddonRepository.RESOURCE_PACKS, ModrinthRemoteAddonRepository.RESOURCE_PACKS); + public static DownloadListPage ofResourcePack(boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.RESOURCE_PACK, CurseForgeRemoteAddonRepository.RESOURCE_PACKS, ModrinthRemoteAddonRepository.RESOURCE_PACKS); } - public static DownloadListPage ofCurseForgeResourcePack(DownloadPage.DownloadCallback callback, boolean versionSelection) { - return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteAddonRepository.Type.RESOURCE_PACK, CurseForgeRemoteAddonRepository.RESOURCE_PACKS, null); + public static DownloadListPage ofCurseForgeResourcePack(boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.RESOURCE_PACK, CurseForgeRemoteAddonRepository.RESOURCE_PACKS, null); } - public static DownloadListPage ofModrinthResourcePack(DownloadPage.DownloadCallback callback, boolean versionSelection) { - return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteAddonRepository.Type.RESOURCE_PACK, null, ModrinthRemoteAddonRepository.RESOURCE_PACKS); + public static DownloadListPage ofModrinthResourcePack(boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.RESOURCE_PACK, null, ModrinthRemoteAddonRepository.RESOURCE_PACKS); } - public static DownloadListPage ofShaderPack(DownloadPage.DownloadCallback callback, boolean versionSelection) { - var page = new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteAddonRepository.Type.SHADER_PACK, CurseForgeRemoteAddonRepository.SHADERS, ModrinthRemoteAddonRepository.SHADER_PACKS); + public static DownloadListPage ofShaderPack(boolean versionSelection) { + var page = new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.SHADER_PACK, CurseForgeRemoteAddonRepository.SHADERS, ModrinthRemoteAddonRepository.SHADER_PACKS); page.supportChinese.set(false); return page; } 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 10ac770a365..7a54d4f309a 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 @@ -460,10 +460,10 @@ final class ModInfoDialog extends JFXDialogLayout { button.setOnAction(e -> { fireEvent(new DialogCloseEvent()); Controllers.navigate(new DownloadPage( - repository instanceof CurseForgeRemoteAddonRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false), + repository instanceof CurseForgeRemoteAddonRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(false) : HMCLLocalizedDownloadListPage.ofModrinthMod(false), remoteAddon, new HMCLGameRepository.InstanceReference(ModListPageSkin.this.getSkinnable().getRepository(), ModListPageSkin.this.getSkinnable().getInstanceId()), - org.jackhuang.hmcl.ui.download.DownloadPage.FOR_MOD + null )); }); button.setDisable(false); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java index 504cc0bfce5..2610043c634 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java @@ -40,8 +40,8 @@ import org.jackhuang.hmcl.addon.*; import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository; import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository; -import org.jackhuang.hmcl.addon.resourcepack.ResourcePackFile; -import org.jackhuang.hmcl.addon.resourcepack.ResourcePackManager; +import org.jackhuang.hmcl.addon.pack.resourcepack.ResourcePackFile; +import org.jackhuang.hmcl.addon.pack.resourcepack.ResourcePackManager; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.setting.SettingsManager; import org.jackhuang.hmcl.setting.DownloadProviders; @@ -616,11 +616,11 @@ private static final class ResourcePackInfoDialog extends JFXDialogLayout { fireEvent(new DialogCloseEvent()); Controllers.navigate(new DownloadPage( repository instanceof CurseForgeRemoteAddonRepository - ? HMCLLocalizedDownloadListPage.ofCurseForgeResourcePack(null, false) - : HMCLLocalizedDownloadListPage.ofModrinthResourcePack(null, false), + ? HMCLLocalizedDownloadListPage.ofCurseForgeResourcePack(false) + : HMCLLocalizedDownloadListPage.ofModrinthResourcePack(false), remoteAddon, new HMCLGameRepository.InstanceReference(page.repository, page.instanceId), - org.jackhuang.hmcl.ui.download.DownloadPage.FOR_RESOURCE_PACK + null )); }); button.setDisable(false); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 199d231bf9c..a273ea6a6be 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -198,6 +198,7 @@ addon.dependency.incompatible=Incompatible Addons (Installing these addons at th addon.dependency.broken=Broken Dependencies (This addon existed before, but it does not exist anymore. Try using another download source.) addon.download.title.release=Minecraft %s addon.download.title.snapshot=Minecraft %s (Snapshots) +addon.install=Install addon.modrinth=Modrinth archive.author=Author(s) @@ -1163,7 +1164,6 @@ mods.restore=Restore mods.url=Official Page mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot launch. Are you sure you want to update? mods.warning.loader_mismatch=Mod loader mismatch -mods.install=Install mods.save_as=Save As mods.unknown=Unknown Mod diff --git a/HMCL/src/main/resources/assets/lang/I18N_ar.properties b/HMCL/src/main/resources/assets/lang/I18N_ar.properties index 58144270e0b..86f1c55edb3 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -183,6 +183,7 @@ addon.check_update.file=الملف addon.check_update.source=المصدر addon.check_update.target_version=الإصدار المستهدف addon.curseforge=CurseForge +addon.install=تثبيت addon.modrinth=Modrinth archive.author=المؤلف/المؤلفون @@ -1108,7 +1109,6 @@ mods.restore=استعادة mods.url=الصفحة الرسمية mods.update_modpack_mod.warning=تحديث المودات في حزمة مودات قد يؤدي إلى نتائج لا يمكن إصلاحها، وقد يتسبب في تلف الحزمة بحيث لا تعمل. هل أنت متأكد من التحديث؟ mods.warning.loader_mismatch=عدم تطابق محمّل المودات -mods.install=تثبيت mods.save_as=حفظ باسم mods.unknown=مود غير معروف diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 6e93385e37d..5fe0f296725 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -164,6 +164,7 @@ addon.check_update.file=Archivo addon.check_update.source=Fuente addon.check_update.target_version=Versión de destino addon.curseforge=CurseForge +addon.install=Instalar addon.modrinth=Modrinth archive.author=Autor(es) @@ -1065,7 +1066,6 @@ mods.not_modded=¡Debes instalar primero un cargador de mods (Forge, NeoForge, F mods.restore=Restaurar mods.url=Página oficial mods.update_modpack_mod.warning=Actualizar mods en un modpack puede generar resultados irreparables, posiblemente corrompiendo el modpack para que no pueda iniciarse. ¿Seguro que quieres actualizar? -mods.install=Instalar mods.save_as=Guardar como nbt.entries=%s entradas diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index 7c8c5eb4f3a..54f23cbedb7 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -877,7 +877,7 @@ mods.not_modded=君須先於「自裝」頁裝鍛、新鍛、緞、褥或輕載 mods.restore=復 mods.url=改囊官網 mods.update_modpack_mod.warning=更迭改囊集之改囊或損之,以不能啟。行而不復,誠更迭乎? -mods.install=裝至是例 +addon.install=裝至是例 mods.save_as=引至私案夾 nbt.entries=%s 元 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index e9093c84542..d9650eed3c1 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -163,6 +163,7 @@ addon.check_update.file=Файл addon.check_update.source=Источник addon.check_update.target_version=Целевая версия addon.curseforge=CurseForge +addon.install=Установить addon.modrinth=Modrinth archive.author=Автор(ы) @@ -1058,7 +1059,6 @@ mods.not_modded=Для управления модами необходимо с mods.restore=Восстановить mods.url=Официальная страница mods.update_modpack_mod.warning=Обновление модов в пакете модов может привести к непоправимым результатам, возможно, повредить пакет модов так, что он не сможет запуститься. Вы уверены, что хотите обновить? -mods.install=Установить mods.save_as=Сохранить как nbt.entries=%s записи diff --git a/HMCL/src/main/resources/assets/lang/I18N_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index 3607e586ebe..e105726b94d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -163,6 +163,7 @@ addon.check_update.file=Файл addon.check_update.source=Джерело addon.check_update.target_version=Цільова версія addon.curseforge=CurseForge +addon.install=Встановити addon.modrinth=Modrinth archive.author=Автор(и) @@ -1006,7 +1007,6 @@ mods.not_modded=Ви повинні спочатку встановити зав mods.restore=Відновити mods.url=Офіційна сторінка mods.update_modpack_mod.warning=Оновлення модів у модпаку може призвести до непоправних наслідків, можливо, пошкодивши модпак так, що він не зможе запуститися. Ви впевнені, що хочете оновити? -mods.install=Встановити mods.save_as=Зберегти як nbt.entries=%s записів diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 9e571a5b40c..b84437fc10b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -196,6 +196,7 @@ addon.dependency.incompatible=不相容附加內容 (與正在下載的檔案同 addon.dependency.broken=損壞的相依內容 (該相依內容曾經存在於附加內容下載源中,但現在已被刪除,請嘗試其他下載源) addon.download.title.release=Minecraft %s addon.download.title.snapshot=Minecraft %s (快照) +addon.install=安裝到目前實例 addon.modrinth=Modrinth archive.author=作者 @@ -960,7 +961,6 @@ mods.restore=回退 mods.url=官方頁面 mods.update_modpack_mod.warning=更新模組包中的模組可能導致模組包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎? mods.warning.loader_mismatch=模組載入器不匹配 -mods.install=安裝到目前實例 mods.save_as=下載到本機目錄 mods.unknown=未知模組 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 3f339510c78..bf37dad8f20 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -198,6 +198,7 @@ addon.dependency.incompatible=不兼容的附加内容 (同时与正在下载的 addon.dependency.broken=损坏的前置内容 (该前置内容曾经在该附加内容下载源上存在过,但现在被删除了,换个下载源试试吧) addon.download.title.release=Minecraft %s addon.download.title.snapshot=Minecraft %s (快照) +addon.install=安装到当前实例 addon.modrinth=Modrinth archive.author=作者 @@ -965,7 +966,6 @@ mods.restore=回退 mods.url=官方页面 mods.update_modpack_mod.warning=更新整合包中的模组可能导致整合包损坏,使整合包无法正常启动。该操作不可逆,确定要更新吗? mods.warning.loader_mismatch=模组加载器不匹配 -mods.install=安装到当前实例 mods.save_as=下载到本地文件夹 mods.unknown=未知模组 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModManager.java index 8acc3151117..9b9540587a1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModManager.java @@ -20,7 +20,7 @@ import com.google.gson.JsonParseException; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.LocalAddonManager; -import org.jackhuang.hmcl.addon.meta.*; +import org.jackhuang.hmcl.addon.mod.meta.*; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.util.Pair; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/FabricModMetadata.java similarity index 98% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/FabricModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/FabricModMetadata.java index 4601ed1ca5d..e7098789f91 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/FabricModMetadata.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.addon.meta; +package org.jackhuang.hmcl.addon.mod.meta; import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeNewModMetadata.java similarity index 99% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeNewModMetadata.java index 6b7ff0085b4..e86c1ca0df7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeNewModMetadata.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.addon.meta; +package org.jackhuang.hmcl.addon.mod.meta; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeOldModMetadata.java similarity index 99% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeOldModMetadata.java index 81f358c94e6..a49e7926351 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeOldModMetadata.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.addon.meta; +package org.jackhuang.hmcl.addon.mod.meta; import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadataLst.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeOldModMetadataLst.java similarity index 95% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadataLst.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeOldModMetadataLst.java index 25909ac0ccd..8d1a1308344 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadataLst.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/ForgeOldModMetadataLst.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.addon.meta; +package org.jackhuang.hmcl.addon.mod.meta; import org.jackhuang.hmcl.util.gson.JsonSerializable; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/LiteModMetadata.java similarity index 98% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/LiteModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/LiteModMetadata.java index aea8c4fab2e..dea7f306a1a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/LiteModMetadata.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.addon.meta; +package org.jackhuang.hmcl.addon.mod.meta; import com.google.gson.JsonParseException; import kala.compress.archivers.zip.ZipArchiveEntry; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/QuiltModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/QuiltModMetadata.java similarity index 98% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/QuiltModMetadata.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/QuiltModMetadata.java index 02e31ed7269..775acbf0f4b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/QuiltModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/meta/QuiltModMetadata.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.addon.meta; +package org.jackhuang.hmcl.addon.mod.meta; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/PackMcMeta.java similarity index 99% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/PackMcMeta.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/PackMcMeta.java index 3cc4d34e43c..824695c4c84 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/PackMcMeta.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.addon.meta; +package org.jackhuang.hmcl.addon.pack; import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/datapack/DataPack.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/datapack/DataPack.java similarity index 99% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/datapack/DataPack.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/datapack/DataPack.java index 6c15efbe74d..593cd0d4f16 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/datapack/DataPack.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/datapack/DataPack.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.addon.datapack; +package org.jackhuang.hmcl.addon.pack.datapack; import com.google.gson.JsonParseException; import javafx.application.Platform; @@ -24,7 +24,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.jackhuang.hmcl.addon.LocalAddonFile; -import org.jackhuang.hmcl.addon.meta.PackMcMeta; +import org.jackhuang.hmcl.addon.pack.PackMcMeta; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java similarity index 97% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java index ffa35e4c227..8d918c1de26 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java @@ -15,11 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.addon.resourcepack; +package org.jackhuang.hmcl.addon.pack.resourcepack; import javafx.scene.image.Image; import org.jackhuang.hmcl.addon.LocalAddonFile; -import org.jackhuang.hmcl.addon.meta.PackMcMeta; +import org.jackhuang.hmcl.addon.pack.PackMcMeta; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jetbrains.annotations.Contract; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java similarity index 96% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFolder.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java index 7267d04923d..a7cc2da586e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java @@ -15,12 +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.addon.resourcepack; +package org.jackhuang.hmcl.addon.pack.resourcepack; import javafx.scene.image.Image; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.addon.RemoteAddon; -import org.jackhuang.hmcl.addon.meta.PackMcMeta; +import org.jackhuang.hmcl.addon.pack.PackMcMeta; import org.jackhuang.hmcl.util.io.FileUtils; import org.jetbrains.annotations.Nullable; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java similarity index 99% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java index d571f6422cf..27cec69126d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java @@ -15,12 +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.addon.resourcepack; +package org.jackhuang.hmcl.addon.pack.resourcepack; import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.addon.LocalAddonManager; -import org.jackhuang.hmcl.addon.meta.PackMcMeta; +import org.jackhuang.hmcl.addon.pack.PackMcMeta; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonSerializable; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java similarity index 97% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java index 7b733539f41..ba514caa5c6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java @@ -15,13 +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.addon.resourcepack; +package org.jackhuang.hmcl.addon.pack.resourcepack; import javafx.scene.image.Image; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.RemoteAddonRepository; -import org.jackhuang.hmcl.addon.meta.PackMcMeta; +import org.jackhuang.hmcl.addon.pack.PackMcMeta; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jetbrains.annotations.Nullable; diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/resourcepack/ResourcePackManagerTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/resourcepack/ResourcePackManagerTest.java index 26ab05a748b..e7c8c570306 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/resourcepack/ResourcePackManagerTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/resourcepack/ResourcePackManagerTest.java @@ -20,8 +20,8 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.junit.jupiter.api.Test; -import static org.jackhuang.hmcl.addon.resourcepack.ResourcePackManager.isMcVersionSupported; -import static org.jackhuang.hmcl.addon.resourcepack.ResourcePackManager.isMcVersionSupportsNewOptionsFormat; +import static org.jackhuang.hmcl.addon.pack.resourcepack.ResourcePackManager.isMcVersionSupported; +import static org.jackhuang.hmcl.addon.pack.resourcepack.ResourcePackManager.isMcVersionSupportsNewOptionsFormat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; From 6a3ab616a0aba47823b67f1013f1dbb0831e639c Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 4 Jul 2026 17:24:56 +0800 Subject: [PATCH 02/36] framework --- .../hmcl/addon/shader/ShaderFile.java | 21 +++++++++++++++ .../hmcl/addon/shader/ShaderFolder.java | 21 +++++++++++++++ .../hmcl/addon/shader/ShaderLoaderType.java | 26 +++++++++++++++++++ .../hmcl/addon/shader/ShaderZipFile.java | 21 +++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java new file mode 100644 index 00000000000..55bf6d7fdc0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java @@ -0,0 +1,21 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.addon.shader; + +public sealed abstract class ShaderFile permits ShaderZipFile, ShaderFolder { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java new file mode 100644 index 00000000000..8acef61a8bf --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java @@ -0,0 +1,21 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.addon.shader; + +public final class ShaderFolder extends ShaderFile { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java new file mode 100644 index 00000000000..34e1626d844 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java @@ -0,0 +1,26 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.addon.shader; + +public enum ShaderLoaderType { + + OPTI_IRIS, + APERTURE, + CANVAS + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java new file mode 100644 index 00000000000..e540a422b0a --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -0,0 +1,21 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.addon.shader; + +public final class ShaderZipFile extends ShaderFile { +} From 48ae4ddc1b9fbc15c8608a2ce95ef6a6063267c5 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 4 Jul 2026 17:54:13 +0800 Subject: [PATCH 03/36] optimize resource pack meta loading --- .../pack/resourcepack/ResourcePackFile.java | 42 ++++++++++++------- .../pack/resourcepack/ResourcePackFolder.java | 24 ++++------- .../resourcepack/ResourcePackManager.java | 2 +- .../resourcepack/ResourcePackZipFile.java | 29 ++++--------- 4 files changed, 45 insertions(+), 52 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java index 8d918c1de26..94ef6ccec03 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java @@ -21,8 +21,8 @@ import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.pack.PackMcMeta; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,32 +31,44 @@ import java.nio.file.Path; import java.util.List; import java.util.Locale; +import java.util.Objects; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public sealed abstract class ResourcePackFile extends LocalAddonFile implements Comparable permits ResourcePackFolder, ResourcePackZipFile { static ResourcePackFile fromFile(ResourcePackManager manager, Path path) throws IOException { - if (isFileResourcePack(path)) { - return Files.isRegularFile(path) ? new ResourcePackZipFile(manager, path) : new ResourcePackFolder(manager, path); - } - return null; + return Files.isRegularFile(path) ? ResourcePackZipFile.load(manager, path) : ResourcePackFolder.load(manager, path); } public static boolean isFileResourcePack(Path file) { if (Files.isDirectory(file)) return Files.isRegularFile(file.resolve("pack.mcmeta")); - if (Files.isRegularFile(file)) return file.toString().toLowerCase(Locale.ROOT).endsWith(".zip"); + if (Files.isRegularFile(file) && file.toString().toLowerCase(Locale.ROOT).endsWith(".zip")) { + try (var zipFileTree = CompressingUtils.openZipFile(file)) { + return zipFileTree.getEntry("pack.mcmeta") != null; + } catch (IOException e) { + LOG.warning("Failed to check if file is resource pack", e); + } + } return false; } protected final ResourcePackManager manager; protected Path file; + protected final PackMcMeta meta; + protected final Image icon; + protected final String fileName; protected final String fileNameWithExtension; private Compatibility compatibility = null; - protected ResourcePackFile(ResourcePackManager manager, Path file) { + protected ResourcePackFile(ResourcePackManager manager, Path file, @NotNull PackMcMeta meta, @Nullable Image icon) { super(); this.manager = manager; this.file = file; + this.meta = Objects.requireNonNull(meta); + this.icon = icon; + this.fileName = StringUtils.parseColorEscapes(FileUtils.getNameWithoutExtension(file)); this.fileNameWithExtension = file.getFileName().toString(); } @@ -112,19 +124,21 @@ public final boolean keepOldFiles() { public void markDisabled() { } - @Nullable - @Contract(pure = true) - public abstract PackMcMeta getMeta(); + public @NotNull PackMcMeta getMeta() { + return meta; + } + + // 64 * 64 + public @Nullable Image getIcon() { + return icon; + } @Nullable public LocalAddonFile.Description getDescription() { - if (getMeta() == null || getMeta().pack() == null) return null; + if (getMeta().pack() == null) return null; return getMeta().pack().description(); } - // 64*64 - public abstract @Nullable Image getIcon(); - @Override public int compareTo(@NotNull ResourcePackFile other) { return this.fileNameWithExtension.compareTo(other.fileNameWithExtension); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java index a7cc2da586e..cd528faa220 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java @@ -32,22 +32,18 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; final class ResourcePackFolder extends ResourcePackFile { - private final PackMcMeta meta; - private final @Nullable Image icon; - - public ResourcePackFolder(ResourcePackManager manager, Path path) { - super(manager, path); + public static @Nullable ResourcePackFolder load(ResourcePackManager manager, Path path) { PackMcMeta meta = null; try { meta = PackMcMeta.fromNonNullJsonFile(path.resolve("pack.mcmeta")); } catch (Exception e) { LOG.warning("Failed to parse resource pack meta", e); } - this.meta = meta; + if (meta == null) return null; byte[] iconData = null; - Image iconTemp = null; + Image icon = null; try { iconData = Files.readAllBytes(path.resolve("pack.png")); } catch (IOException e) { @@ -55,22 +51,16 @@ public ResourcePackFolder(ResourcePackManager manager, Path path) { } if (iconData != null) { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { - iconTemp = new Image(inputStream, 64, 64, true, true); + icon = new Image(inputStream, 64, 64, true, true); } catch (Exception e) { LOG.warning("Failed to load resource pack icon", e); } } - this.icon = iconTemp; + return new ResourcePackFolder(manager, path, meta, icon); } - @Override - public PackMcMeta getMeta() { - return meta; - } - - @Override - public @Nullable Image getIcon() { - return icon; + public ResourcePackFolder(ResourcePackManager manager, Path path, PackMcMeta meta, @Nullable Image icon) { + super(manager, path, meta, icon); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java index 27cec69126d..43707fe5070 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java @@ -502,7 +502,7 @@ public Stream> arePacksEnabled(Stream Date: Sat, 4 Jul 2026 18:48:46 +0800 Subject: [PATCH 04/36] update --- .../jackhuang/hmcl/addon/LocalAddonFile.java | 3 +- .../hmcl/addon/LocalAddonManager.java | 4 +- .../pack/resourcepack/ResourcePackFolder.java | 2 +- .../resourcepack/ResourcePackZipFile.java | 3 +- .../CurseForgeRemoteAddonRepository.java | 12 ++--- .../ModrinthRemoteAddonRepository.java | 4 +- .../hmcl/addon/shader/ApertureData.java | 31 +++++++++++ .../hmcl/addon/shader/ShaderFile.java | 54 ++++++++++++++++++- .../hmcl/addon/shader/ShaderFolder.java | 24 ++++++++- .../hmcl/addon/shader/ShaderZipFile.java | 38 ++++++++++++- 10 files changed, 158 insertions(+), 17 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureData.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index ab6c9d4aeca..250b29a58c7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -52,8 +52,7 @@ public boolean isDisabled() { public abstract void delete() throws IOException; - @Nullable - public abstract AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException; + public abstract @Nullable AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException; public record AddonUpdate( LocalAddonFile localAddonFile, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonManager.java index 0520b67a8fc..f636317b128 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonManager.java @@ -94,7 +94,7 @@ public Path setOld(T modFile, boolean old) throws IOException { } } - private Path backupFile(Path file) throws IOException { + public static Path backupFile(Path file) throws IOException { Path newPath = file.resolveSibling( StringUtils.addSuffix( StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION), @@ -107,7 +107,7 @@ private Path backupFile(Path file) throws IOException { return newPath; } - private Path restoreFile(Path file) throws IOException { + public static Path restoreFile(Path file) throws IOException { Path newPath = file.resolveSibling( StringUtils.removeSuffix(FileUtils.getName(file), OLD_EXTENSION) ); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java index cd528faa220..40fa8ea86c1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java @@ -59,7 +59,7 @@ final class ResourcePackFolder extends ResourcePackFile { return new ResourcePackFolder(manager, path, meta, icon); } - public ResourcePackFolder(ResourcePackManager manager, Path path, PackMcMeta meta, @Nullable Image icon) { + private ResourcePackFolder(ResourcePackManager manager, Path path, PackMcMeta meta, @Nullable Image icon) { super(manager, path, meta, icon); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java index 8c159bc301b..436bf420157 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java @@ -36,6 +36,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; final class ResourcePackZipFile extends ResourcePackFile { + public static @Nullable ResourcePackZipFile load(ResourcePackManager manager, Path path) throws IOException { PackMcMeta meta = null; byte[] iconData = null; @@ -69,7 +70,7 @@ final class ResourcePackZipFile extends ResourcePackFile { return new ResourcePackZipFile(manager, path, meta, icon); } - private ResourcePackZipFile(ResourcePackManager manager, Path path, PackMcMeta meta, @Nullable Image icon) throws IOException { + private ResourcePackZipFile(ResourcePackManager manager, Path path, PackMcMeta meta, @Nullable Image icon) { super(manager, path, meta, icon); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java index 3d12959d30f..e5422b1b701 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java @@ -138,7 +138,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion response = withApiKey(HttpRequest.GET(candidate.toString())) .getJson(Response.typeOf(listTypeOf(CurseAddon.class))); if (searchFilter.isEmpty()) { - return new SearchResult(response.data().stream().map(addon -> addon.toMod(type)), calculateTotalPages(response, pageSize)); + return new SearchResult(response.data().stream().map(addon -> addon.toAddon(type)), calculateTotalPages(response, pageSize)); } break; } catch (IOException e) { @@ -167,7 +167,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator(); - return new SearchResult(response.data().stream().map(addon -> addon.toMod(type)).map(remoteMod -> { + return new SearchResult(response.data().stream().map(addon -> addon.toAddon(type)).map(remoteMod -> { String lowerCaseResult = remoteMod.title().toLowerCase(Locale.ROOT); int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult); @@ -178,7 +178,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion } return pair(remoteMod, diff); - }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.data().stream().map(addon -> addon.toMod(type)), calculateTotalPages(response, pageSize)); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.data().stream().map(addon -> addon.toAddon(type)), calculateTotalPages(response, pageSize)); } finally { SEMAPHORE.release(); } @@ -227,7 +227,7 @@ public RemoteAddon getModById(DownloadProvider downloadProvider, String id) thro try { Response response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id)) .getJson(Response.typeOf(CurseAddon.class)); - return response.data.toMod(type); + return response.data.toAddon(type); } finally { SEMAPHORE.release(); } @@ -379,7 +379,7 @@ public Stream loadVersions(RemoteAddonRepository modReposit return modRepository.getRemoteVersionsById(downloadProvider, Integer.toString(id)); } - public RemoteAddon toMod(Type type) { + public RemoteAddon toAddon(Type type) { String iconUrl = ""; if (logo != null) { if (StringUtils.isNotBlank(logo.thumbnailUrl())) @@ -393,7 +393,7 @@ else if (StringUtils.isNotBlank(logo.url())) "", name, summary, - categories.stream().map(category -> Integer.toString(category.getId())).collect(Collectors.toList()), + categories.stream().map(category -> Integer.toString(category.getId())).toList(), links.websiteUrl, iconUrl, this, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java index c4deb57d091..196de7c78e5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java @@ -153,7 +153,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion LOG.info("Fetching " + candidate); Response response = HttpRequest.GET(candidate.toString()) .getJson(Response.typeOf(ProjectSearchResult.class)); - return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize)); + return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toAddon), (int) Math.ceil((double) response.totalHits / pageSize)); } catch (IOException e) { LOG.warning("Failed to search addons: " + candidate, e); @@ -463,7 +463,7 @@ public Stream loadVersions(RemoteAddonRepository modReposit return modRepository.getRemoteVersionsById(downloadProvider, projectId()); } - public RemoteAddon toMod() { + public RemoteAddon toAddon() { Type type = switch (projectType) { case "modpack" -> Type.MODPACK; case "resourcepack" -> Type.RESOURCE_PACK; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureData.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureData.java new file mode 100644 index 00000000000..bd0702fa797 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureData.java @@ -0,0 +1,31 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.addon.shader; + +import javafx.scene.image.Image; +import org.jackhuang.hmcl.util.gson.JsonSerializable; +import org.jetbrains.annotations.Nullable; + +@JsonSerializable +public record ApertureData(ApertureMeta meta, @Nullable Image icon) { + + @JsonSerializable + public record ApertureMeta(String name, String version){ + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java index 55bf6d7fdc0..66953a8a6ed 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java @@ -17,5 +17,57 @@ */ package org.jackhuang.hmcl.addon.shader; -public sealed abstract class ShaderFile permits ShaderZipFile, ShaderFolder { +import org.jackhuang.hmcl.addon.LocalAddonFile; +import org.jackhuang.hmcl.addon.LocalAddonManager; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Path; + +public sealed abstract class ShaderFile extends LocalAddonFile permits ShaderZipFile, ShaderFolder { + + protected Path file; + protected final String fileNameWithoutExtension; + protected final ShaderLoaderType loaderType; + protected final @Nullable ApertureData apertureData; + + protected ShaderFile(Path file, ShaderLoaderType loaderType, @Nullable ApertureData apertureData) { + this.file = file; + this.fileNameWithoutExtension = FileUtils.getNameWithoutExtension(file); + this.loaderType = loaderType; + this.apertureData = apertureData; + } + + @Override + public Path getFile() { + return file; + } + + @Override + public String getFileName() { + return fileNameWithoutExtension; + } + + @Override + public boolean isDisabled() { + return false; + } + + @Override + public void markDisabled() { + // NO-OP + } + + @Override + public boolean keepOldFiles() { + return false; + } + + @Override + public void setOld(boolean old) throws IOException { + if (old) file = LocalAddonManager.backupFile(file); + else file = LocalAddonManager.restoreFile(file); + } + } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java index 8acef61a8bf..4b9fd1f9812 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java @@ -17,5 +17,27 @@ */ package org.jackhuang.hmcl.addon.shader; -public final class ShaderFolder extends ShaderFile { +import org.jackhuang.hmcl.addon.RemoteAddon; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Path; + +final class ShaderFolder extends ShaderFile { + + private ShaderFolder(Path file, ShaderLoaderType loaderType, @Nullable ApertureData apertureData) { + super(file, loaderType, apertureData); + } + + @Override + public void delete() throws IOException { + FileUtils.deleteDirectory(file); + } + + @Override + public @Nullable AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { + return null; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index e540a422b0a..2eadd965d57 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -17,5 +17,41 @@ */ package org.jackhuang.hmcl.addon.shader; -public final class ShaderZipFile extends ShaderFile { +import org.jackhuang.hmcl.addon.RemoteAddon; +import org.jackhuang.hmcl.addon.RemoteAddonRepository; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +final class ShaderZipFile extends ShaderFile { + + private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ApertureData apertureData) { + super(file, loaderType, apertureData); + } + + @Override + public void delete() throws IOException { + Files.deleteIfExists(file); + } + + @Override + public @Nullable AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { + RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.SHADER_PACK); + if (repository == null) return null; + Optional currentVersion = repository.getRemoteVersionByLocalFile(file); + if (currentVersion.isEmpty()) return null; + List remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().modid()) + .filter(version -> version.gameVersions().contains(gameVersion)) //TODO loader + .filter(version -> version.datePublished().compareTo(currentVersion.get().datePublished()) > 0) + .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) + .toList(); + if (remoteVersions.isEmpty()) return null; + return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), false); + } } From a06a481fb781bba64a6505ec8ff651d34c572154 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 4 Jul 2026 18:53:50 +0800 Subject: [PATCH 05/36] update --- .../org/jackhuang/hmcl/addon/LoaderType.java | 21 +++++++++++++++++++ .../hmcl/addon/mod/ModLoaderType.java | 4 +++- .../hmcl/addon/shader/ShaderLoaderType.java | 4 +++- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java new file mode 100644 index 00000000000..822cb6e2eb5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java @@ -0,0 +1,21 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.addon; + +public interface LoaderType { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModLoaderType.java index 61ba46cea84..375726975e5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModLoaderType.java @@ -17,7 +17,9 @@ */ package org.jackhuang.hmcl.addon.mod; -public enum ModLoaderType { +import org.jackhuang.hmcl.addon.LoaderType; + +public enum ModLoaderType implements LoaderType { UNKNOWN, FORGE, CLEANROOM, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java index 34e1626d844..f93ec787fa5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java @@ -17,7 +17,9 @@ */ package org.jackhuang.hmcl.addon.shader; -public enum ShaderLoaderType { +import org.jackhuang.hmcl.addon.LoaderType; + +public enum ShaderLoaderType implements LoaderType { OPTI_IRIS, APERTURE, From 1f4e7fa211197af63aec47a3ebcd734e0c4773a0 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 5 Jul 2026 12:16:00 +0800 Subject: [PATCH 06/36] update --- HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java index 822cb6e2eb5..f70b31dbeeb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java @@ -17,5 +17,6 @@ */ package org.jackhuang.hmcl.addon; +/// For mods and shaders public interface LoaderType { } From 41c422186308e8b47a64c44d9a1d517279d547dc Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 5 Jul 2026 17:38:13 +0800 Subject: [PATCH 07/36] update --- .../java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java index f93ec787fa5..6721fab957a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java @@ -22,7 +22,6 @@ public enum ShaderLoaderType implements LoaderType { OPTI_IRIS, - APERTURE, - CANVAS + APERTURE } From a8ee78ff939891933eadcc0b82181d3b8f63ce42 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 5 Jul 2026 18:01:14 +0800 Subject: [PATCH 08/36] update --- .../org/jackhuang/hmcl/addon/LoaderType.java | 5 ++++ .../hmcl/addon/mod/ModLoaderType.java | 27 ++++++++++++++----- .../hmcl/addon/shader/ShaderLoaderType.java | 15 +++++++++-- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java index f70b31dbeeb..eaf5fa6bc34 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java @@ -17,6 +17,11 @@ */ package org.jackhuang.hmcl.addon; +import java.util.Set; + /// For mods and shaders public interface LoaderType { + + Set names(); + } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModLoaderType.java index 375726975e5..bc9a5f91f0b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModLoaderType.java @@ -19,13 +19,26 @@ import org.jackhuang.hmcl.addon.LoaderType; +import java.util.Set; + public enum ModLoaderType implements LoaderType { UNKNOWN, - FORGE, - CLEANROOM, - NEO_FORGE, - FABRIC, - QUILT, - LITE_LOADER, - LEGACY_FABRIC + FORGE("forge"), + CLEANROOM("cleanroom"), + NEO_FORGE("neoforge"), + FABRIC("fabric"), + QUILT("quilt"), + LITE_LOADER("liteloader"), + LEGACY_FABRIC("legacy-fabric"); + + private final Set names; + + ModLoaderType(String... names) { + this.names = Set.of(names); + } + + @Override + public Set names() { + return names; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java index 6721fab957a..a42c757db35 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java @@ -19,9 +19,20 @@ import org.jackhuang.hmcl.addon.LoaderType; +import java.util.Set; + public enum ShaderLoaderType implements LoaderType { + OPTIFINE_IRIS("optifine", "iris"), + APERTURE("aperture"); + + private final Set names; - OPTI_IRIS, - APERTURE + ShaderLoaderType(String... names) { + this.names = Set.of(names); + } + @Override + public Set names() { + return names; + } } From 05099f776ad0ca79c8464e7ae16d7c4c97dd97f8 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 5 Jul 2026 18:15:30 +0800 Subject: [PATCH 09/36] update --- .../org/jackhuang/hmcl/addon/LocalAddonFile.java | 9 +++++++-- .../org/jackhuang/hmcl/addon/mod/LocalModFile.java | 6 ------ .../pack/resourcepack/ResourcePackFolder.java | 13 ------------- .../pack/resourcepack/ResourcePackZipFile.java | 6 ------ .../jackhuang/hmcl/addon/shader/ShaderFile.java | 7 ++++++- .../jackhuang/hmcl/addon/shader/ShaderFolder.java | 14 -------------- .../jackhuang/hmcl/addon/shader/ShaderZipFile.java | 6 ------ .../java/org/jackhuang/hmcl/util/io/FileUtils.java | 8 ++++++++ 8 files changed, 21 insertions(+), 48 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 250b29a58c7..e9f99a16fe9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -50,9 +50,14 @@ public boolean isDisabled() { public abstract boolean keepOldFiles(); - public abstract void delete() throws IOException; + public void delete() throws IOException { + FileUtils.forceDeleteIfExists(getFile()); + } - public abstract @Nullable AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException; + @Nullable + public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { + return null; + } public record AddonUpdate( LocalAddonFile localAddonFile, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index f4bfafab115..b7c10d28264 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -27,7 +27,6 @@ import org.jackhuang.hmcl.util.io.FileUtils; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -189,11 +188,6 @@ public void markDisabled() throws IOException { file = modManager.disableMod(file); } - @Override - public void delete() throws IOException { - Files.deleteIfExists(file); - } - @Override public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.MOD); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java index 40fa8ea86c1..607f2775c41 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java @@ -18,10 +18,7 @@ package org.jackhuang.hmcl.addon.pack.resourcepack; import javafx.scene.image.Image; -import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.pack.PackMcMeta; -import org.jackhuang.hmcl.util.io.FileUtils; import org.jetbrains.annotations.Nullable; import java.io.ByteArrayInputStream; @@ -62,14 +59,4 @@ final class ResourcePackFolder extends ResourcePackFile { private ResourcePackFolder(ResourcePackManager manager, Path path, PackMcMeta meta, @Nullable Image icon) { super(manager, path, meta, icon); } - - @Override - public void delete() throws IOException { - FileUtils.deleteDirectory(file); - } - - @Override - public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) { - return null; - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java index 436bf420157..ffeabab2e0c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java @@ -27,7 +27,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; import java.util.List; @@ -74,11 +73,6 @@ private ResourcePackZipFile(ResourcePackManager manager, Path path, PackMcMeta m super(manager, path, meta, icon); } - @Override - public void delete() throws IOException { - Files.deleteIfExists(file); - } - @Override public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.RESOURCE_PACK); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java index 66953a8a6ed..881feac27da 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java @@ -20,12 +20,13 @@ import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.LocalAddonManager; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.file.Path; -public sealed abstract class ShaderFile extends LocalAddonFile permits ShaderZipFile, ShaderFolder { +public sealed abstract class ShaderFile extends LocalAddonFile implements Comparable permits ShaderZipFile, ShaderFolder { protected Path file; protected final String fileNameWithoutExtension; @@ -70,4 +71,8 @@ public void setOld(boolean old) throws IOException { else file = LocalAddonManager.restoreFile(file); } + @Override + public int compareTo(@NotNull ShaderFile other) { + return fileNameWithoutExtension.compareTo(other.fileNameWithoutExtension); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java index 4b9fd1f9812..22f6cff95b6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java @@ -17,12 +17,8 @@ */ package org.jackhuang.hmcl.addon.shader; -import org.jackhuang.hmcl.addon.RemoteAddon; -import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.util.io.FileUtils; import org.jetbrains.annotations.Nullable; -import java.io.IOException; import java.nio.file.Path; final class ShaderFolder extends ShaderFile { @@ -30,14 +26,4 @@ final class ShaderFolder extends ShaderFile { private ShaderFolder(Path file, ShaderLoaderType loaderType, @Nullable ApertureData apertureData) { super(file, loaderType, apertureData); } - - @Override - public void delete() throws IOException { - FileUtils.deleteDirectory(file); - } - - @Override - public @Nullable AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { - return null; - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index 2eadd965d57..f2e3b02ee01 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -23,7 +23,6 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; import java.util.List; @@ -35,11 +34,6 @@ private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable Aperture super(file, loaderType, apertureData); } - @Override - public void delete() throws IOException { - Files.deleteIfExists(file); - } - @Override public @Nullable AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.SHADER_PACK); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java index c583860bcc8..e3268dc77c4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java @@ -468,6 +468,14 @@ public static void forceDelete(Path file) Files.delete(file); } + public static void forceDeleteIfExists(Path file) + throws IOException { + if (Files.isDirectory(file)) + deleteDirectory(file); + else + Files.deleteIfExists(file); + } + public static void copyFile(Path srcFile, Path destFile) throws IOException { Objects.requireNonNull(srcFile, "Source must not be null"); From 28ac6898b19ae40e25ba327bf20ca3118637a57f Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 5 Jul 2026 20:22:27 +0800 Subject: [PATCH 10/36] loading --- .../hmcl/ui/versions/ShaderListPage.java | 21 ++++++++ .../jackhuang/hmcl/addon/pack/PackMcMeta.java | 6 +-- .../pack/resourcepack/ResourcePackFile.java | 2 +- .../{ApertureData.java => ApertureMeta.java} | 9 +--- .../hmcl/addon/shader/ShaderFile.java | 13 +++-- .../hmcl/addon/shader/ShaderFolder.java | 41 +++++++++++++- .../hmcl/addon/shader/ShaderZipFile.java | 53 ++++++++++++++++++- .../jackhuang/hmcl/util/gson/JsonUtils.java | 10 ++++ .../resourcepack/ResourcePackManagerTest.java | 4 +- 9 files changed, 137 insertions(+), 22 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/{ApertureData.java => ApertureMeta.java} (78%) rename HMCLCore/src/test/java/org/jackhuang/hmcl/{ => addon/pack}/resourcepack/ResourcePackManagerTest.java (95%) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java new file mode 100644 index 00000000000..fddcc0739b1 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java @@ -0,0 +1,21 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.versions; + +public class ShaderListPage { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/PackMcMeta.java index 824695c4c84..76507488c9f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/PackMcMeta.java @@ -40,10 +40,8 @@ @JsonSerializable public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Validation { - private static final Gson LENIENT_GSON = JsonUtils.defaultGsonBuilder().setStrictness(Strictness.LENIENT).create(); - public static PackMcMeta fromNonNullJson(String jsonString) throws JsonParseException { - PackMcMeta parsed = LENIENT_GSON.fromJson(jsonString, PackMcMeta.class); + PackMcMeta parsed = JsonUtils.LENIENT_GSON.fromJson(jsonString, PackMcMeta.class); if (parsed == null) throw new JsonParseException("Json object cannot be null."); return parsed; @@ -51,7 +49,7 @@ public static PackMcMeta fromNonNullJson(String jsonString) throws JsonParseExce public static PackMcMeta fromNonNullJsonFile(Path jsonFile) throws JsonParseException, IOException { try (var reader = Files.newBufferedReader(jsonFile)) { - PackMcMeta parsed = LENIENT_GSON.fromJson(reader, PackMcMeta.class); + PackMcMeta parsed = JsonUtils.LENIENT_GSON.fromJson(reader, PackMcMeta.class); if (parsed == null) throw new JsonParseException("Json object cannot be null."); return parsed; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java index 94ef6ccec03..e9c15697aa5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java @@ -36,7 +36,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; public sealed abstract class ResourcePackFile extends LocalAddonFile implements Comparable permits ResourcePackFolder, ResourcePackZipFile { - static ResourcePackFile fromFile(ResourcePackManager manager, Path path) throws IOException { + static @Nullable ResourcePackFile fromFile(ResourcePackManager manager, Path path) throws IOException { return Files.isRegularFile(path) ? ResourcePackZipFile.load(manager, path) : ResourcePackFolder.load(manager, path); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureData.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java similarity index 78% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureData.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java index bd0702fa797..d3503d35d01 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureData.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java @@ -17,15 +17,8 @@ */ package org.jackhuang.hmcl.addon.shader; -import javafx.scene.image.Image; import org.jackhuang.hmcl.util.gson.JsonSerializable; -import org.jetbrains.annotations.Nullable; @JsonSerializable -public record ApertureData(ApertureMeta meta, @Nullable Image icon) { - - @JsonSerializable - public record ApertureMeta(String name, String version){ - } - +public record ApertureMeta(String name, String version) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java index 881feac27da..c466df268a7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.addon.shader; +import javafx.scene.image.Image; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.LocalAddonManager; import org.jackhuang.hmcl.util.io.FileUtils; @@ -24,20 +25,26 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; public sealed abstract class ShaderFile extends LocalAddonFile implements Comparable permits ShaderZipFile, ShaderFolder { + public static @Nullable ShaderFile fromFile(Path file) throws IOException { + return Files.isRegularFile(file) ? ShaderZipFile.load(file) : ShaderFolder.load(file); + } protected Path file; protected final String fileNameWithoutExtension; protected final ShaderLoaderType loaderType; - protected final @Nullable ApertureData apertureData; + protected final @Nullable ApertureMeta apertureMeta; + protected final @Nullable Image icon; - protected ShaderFile(Path file, ShaderLoaderType loaderType, @Nullable ApertureData apertureData) { + protected ShaderFile(Path file, ShaderLoaderType loaderType, @Nullable ApertureMeta apertureMeta, @Nullable Image icon) { this.file = file; this.fileNameWithoutExtension = FileUtils.getNameWithoutExtension(file); this.loaderType = loaderType; - this.apertureData = apertureData; + this.apertureMeta = apertureMeta; + this.icon = icon; } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java index 22f6cff95b6..560918e33c0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java @@ -17,13 +17,50 @@ */ package org.jackhuang.hmcl.addon.shader; +import javafx.scene.image.Image; +import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jetbrains.annotations.Nullable; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + final class ShaderFolder extends ShaderFile { - private ShaderFolder(Path file, ShaderLoaderType loaderType, @Nullable ApertureData apertureData) { - super(file, loaderType, apertureData); + public static @Nullable ShaderFolder load(Path file) { + Path shadersPath = file.resolve("shaders"); + if (!Files.isDirectory(shadersPath)) return null; + + if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture + ApertureMeta meta = null; + try { + meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ApertureMeta.class); + } catch (IOException e) { + LOG.warning("Failed to load aperture shader metadata", e); + } + byte[] iconData = null; + Image icon = null; + try { + iconData = Files.readAllBytes(shadersPath.resolve("pack.png")); + } catch (IOException e) { + LOG.warning("Failed to read aperture shader icon", e); + } + if (iconData != null) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { + icon = new Image(inputStream, 64, 64, true, true); + } catch (Exception e) { + LOG.warning("Failed to load aperture shader icon", e); + } + } + return new ShaderFolder(file, ShaderLoaderType.APERTURE, meta, icon); + } + return new ShaderFolder(file, ShaderLoaderType.OPTIFINE_IRIS, null, null); + } + + private ShaderFolder(Path file, ShaderLoaderType loaderType, @Nullable ApertureMeta apertureMeta, @Nullable Image icon) { + super(file, loaderType, apertureMeta, icon); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index f2e3b02ee01..efda0ebe4ad 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -17,21 +17,70 @@ */ package org.jackhuang.hmcl.addon.shader; +import javafx.scene.image.Image; import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jetbrains.annotations.Nullable; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; import java.util.List; +import java.util.Locale; import java.util.Optional; +import java.util.stream.Stream; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; final class ShaderZipFile extends ShaderFile { - private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ApertureData apertureData) { - super(file, loaderType, apertureData); + public static @Nullable ShaderZipFile load(Path file) throws IOException { + if (!Files.isRegularFile(file) || !file.toString().toLowerCase(Locale.ROOT).endsWith(".zip")) return null; + + try (var zipSystem = CompressingUtils.createReadOnlyZipFileSystem(file)) { + Path root = zipSystem.getRootDirectories().iterator().next(); + try (Stream stream = Files.walk(root)) { + Path shadersPath = stream.filter(Files::isDirectory) + .filter(path -> path.endsWith("shaders")) + .findFirst().orElse(null); + if (shadersPath == null) return null; + if (!Files.isDirectory(shadersPath)) return null; + + if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture + ApertureMeta meta = null; + try { + meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ApertureMeta.class); + } catch (IOException e) { + LOG.warning("Failed to load aperture shader metadata", e); + } + byte[] iconData = null; + Image icon = null; + try { + iconData = Files.readAllBytes(shadersPath.resolve("pack.png")); + } catch (IOException e) { + LOG.warning("Failed to read aperture shader icon", e); + } + if (iconData != null) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { + icon = new Image(inputStream, 64, 64, true, true); + } catch (Exception e) { + LOG.warning("Failed to load aperture shader icon", e); + } + } + return new ShaderZipFile(file, ShaderLoaderType.APERTURE, meta, icon); + } + return new ShaderZipFile(file, ShaderLoaderType.OPTIFINE_IRIS, null, null); + } + } + } + + private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ApertureMeta apertureMeta, @Nullable Image icon) { + super(file, loaderType, apertureMeta, icon); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java index 0451c92ea90..08a0670df0e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java @@ -79,6 +79,16 @@ public final class JsonUtils { .registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory.INSTANCE) .create(); + /// The lenient [Gson] instance. + /// + /// Configured with: + /// - Pretty printing enabled. + /// - Complex map key serialization enabled. + /// - Type adapters for [java.time.Instant], [java.util.UUID], and [java.nio.file.Path]. + /// - [ValidationTypeAdapterFactory], [LowerCaseEnumTypeAdapterFactory], and + /// [JsonTypeAdapterFactory]. + public static final Gson LENIENT_GSON = defaultGsonBuilder().setStrictness(Strictness.LENIENT).create(); + /// Not instantiable. private JsonUtils() { } diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/resourcepack/ResourcePackManagerTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManagerTest.java similarity index 95% rename from HMCLCore/src/test/java/org/jackhuang/hmcl/resourcepack/ResourcePackManagerTest.java rename to HMCLCore/src/test/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManagerTest.java index e7c8c570306..eb990917c78 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/resourcepack/ResourcePackManagerTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManagerTest.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2025 huangyuhui and contributors + * Copyright (C) 2026 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 @@ -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.resourcepack; +package org.jackhuang.hmcl.addon.pack.resourcepack; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.junit.jupiter.api.Test; From 88b0aeecb041bcb2641758e022f993592da6e16d Mon Sep 17 00:00:00 2001 From: ToobLac Date: Mon, 6 Jul 2026 12:57:02 +0800 Subject: [PATCH 11/36] roughly usable --- .../hmcl/ui/download/DownloadPage.java | 13 +- .../ui/versions/AddonCheckUpdatesTask.java | 4 +- .../hmcl/ui/versions/AddonUpdatesPage.java | 34 +- .../HMCLLocalizedDownloadListPage.java | 8 + .../hmcl/ui/versions/ModListPage.java | 4 +- .../ui/versions/ResourcePackListPage.java | 16 +- .../hmcl/ui/versions/ShaderListPage.java | 515 +++++++++++++++++- .../hmcl/ui/versions/VersionPage.java | 9 +- .../main/resources/assets/img/opti-iris.png | Bin 0 -> 8143 bytes .../resources/assets/lang/I18N.properties | 8 +- .../resources/assets/lang/I18N_ar.properties | 2 +- .../resources/assets/lang/I18N_es.properties | 3 +- .../resources/assets/lang/I18N_ja.properties | 3 +- .../resources/assets/lang/I18N_lzh.properties | 2 +- .../resources/assets/lang/I18N_ru.properties | 3 +- .../resources/assets/lang/I18N_uk.properties | 3 +- .../resources/assets/lang/I18N_zh.properties | 2 +- .../assets/lang/I18N_zh_CN.properties | 2 +- .../jackhuang/hmcl/addon/LocalAddonFile.java | 3 + .../resourcepack/ResourcePackManager.java | 6 +- .../hmcl/addon/shader/ApertureMeta.java | 2 + .../hmcl/addon/shader/ShaderFile.java | 31 ++ .../hmcl/addon/shader/ShaderZipFile.java | 22 +- .../hmcl/game/DefaultGameRepository.java | 5 + .../jackhuang/hmcl/game/GameRepository.java | 6 + .../org/jackhuang/hmcl/util/io/FileUtils.java | 10 + 26 files changed, 667 insertions(+), 49 deletions(-) create mode 100644 HMCL/src/main/resources/assets/img/opti-iris.png 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 3315589a9d0..cc855a52604 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 @@ -117,7 +117,7 @@ public DownloadPage(String updateVersion) { .startCategory(i18n("download.content").toUpperCase(Locale.ROOT)) .addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.EXTENSION, SVG.EXTENSION_FILL) .addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE) - .addNavigationDrawerTab(tab, shaderTab, i18n("download.shader"), SVG.WB_SUNNY, SVG.WB_SUNNY_FILL) + .addNavigationDrawerTab(tab, shaderTab, i18n("shaderpack"), SVG.WB_SUNNY, SVG.WB_SUNNY_FILL) .addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.PUBLIC); FXUtils.setLimitWidth(sideBar, 200); setLeft(sideBar); @@ -214,14 +214,19 @@ public void showModpackDownloads() { tab.select(modpackTab, false); } + public DownloadListPage showModDownloads() { + tab.select(modTab, false); + return modTab.getNode(); + } + public DownloadListPage showResourcePackDownloads() { tab.select(resourcePackTab, false); return resourcePackTab.getNode(); } - public DownloadListPage showModDownloads() { - tab.select(modTab, false); - return modTab.getNode(); + public DownloadListPage showShaderDownloads() { + tab.select(shaderTab, false); + return shaderTab.getNode(); } public void showWorldDownloads() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java index 47597d34a63..df72a44a957 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java @@ -30,11 +30,11 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class AddonCheckUpdatesTask extends Task> { +public class AddonCheckUpdatesTask extends Task> { private final DownloadProvider downloadProvider; private final List> dependents; - public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection addons) { + public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection addons) { this.downloadProvider = downloadProvider; dependents = addons.stream().map(addon -> Task.supplyAsync(Schedulers.io(), () -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java index b9c251715f6..320a26e7f69 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java @@ -60,15 +60,15 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class AddonUpdatesPage extends BorderPane implements DecoratorPage { +public class AddonUpdatesPage extends BorderPane implements DecoratorPage { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("addon.check_update"))); - private final LocalAddonManager localAddonManager; + private final Path addonsDirectory; private final ObservableList objects; @SuppressWarnings("unchecked") - public AddonUpdatesPage(LocalAddonManager localAddonManager, List updates) { - this.localAddonManager = localAddonManager; + public AddonUpdatesPage(Path addonsDirectory, List updates) { + this.addonsDirectory = addonsDirectory; getStyleClass().add("gray-background"); @@ -132,7 +132,7 @@ private void setupCellValueFactory(TableColumn column, private void updateFiles() { AddonUpdateTask task = new AddonUpdateTask( - localAddonManager.getDirectory(), + addonsDirectory, objects.stream() .filter(AddonUpdateObject::isEnabled) .map(AddonUpdateObject::getData) @@ -294,19 +294,18 @@ public static class AddonUpdateTask extends Task { RemoteAddon.Version remote = addon.targetVersion(); boolean isDisabled = local.isDisabled(); String originalFileName = local.getFile().getFileName().toString(); + String fileName = addon.useRemoteFileName() ? remote.file().filename() : originalFileName; + if (isDisabled) + fileName = StringUtils.addSuffix(fileName, LocalAddonManager.DISABLED_EXTENSION); + String newFileName = fileName; dependents.add(Task .runAsync(Schedulers.javafx(), () -> local.setOld(true)) .thenComposeAsync(() -> { - String fileName = addon.useRemoteFileName() ? remote.file().filename() : originalFileName; - if (isDisabled) - fileName = StringUtils.addSuffix(fileName, LocalAddonManager.DISABLED_EXTENSION); - var task = new FileDownloadTask( remote.file().url(), - addonDirectory.resolve(fileName) + addonDirectory.resolve(newFileName) ); - task.setName(remote.name()); return task; }) @@ -317,11 +316,14 @@ public static class AddonUpdateTask extends Task { if (isDisabled) local.markDisabled(); failedAddons.add(local); - } else if (!local.keepOldFiles()) { - try { - local.delete(); - } catch (IOException e) { - LOG.warning("Failed to delete outdated addon: " + local.getFile(), e); + } else { + local.onUpdated(newFileName); + if (!local.keepOldFiles()) { + try { + local.delete(); + } catch (IOException e) { + LOG.warning("Failed to delete outdated addon: " + local.getFile(), e); + } } } }) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java index bc52cef4cf1..e6ccf917345 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java @@ -64,6 +64,14 @@ public static DownloadListPage ofShaderPack(boolean versionSelection) { return page; } + public static DownloadListPage ofCurseForgeShaderPack(boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.SHADER_PACK, CurseForgeRemoteAddonRepository.SHADERS, null); + } + + public static DownloadListPage ofModrinthShaderPack(boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(null, versionSelection, RemoteAddonRepository.Type.SHADER_PACK, null, ModrinthRemoteAddonRepository.SHADER_PACKS); + } + private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection, RemoteAddonRepository.Type type, CurseForgeRemoteAddonRepository curseForge, ModrinthRemoteAddonRepository modrinth) { super(null, callback, versionSelection); 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 5207aab98e7..f8f79fabe1c 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 @@ -239,7 +239,7 @@ public void checkUpdates(Collection mods) { Runnable action = () -> Controllers.taskDialog(Task .composeAsync(() -> { Optional gameVersion = repository.getGameVersion(instanceId); - return gameVersion.map(g -> new AddonCheckUpdatesTask<>(DownloadProviders.getDownloadProvider(), g, mods)).orElse(null); + return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, mods)).orElse(null); }) .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception instanceof CancellationException) return; @@ -248,7 +248,7 @@ public void checkUpdates(Collection mods) { } else if (result.isEmpty()) { Controllers.dialog(i18n("addon.check_update.empty")); } else { - Controllers.navigateForward(new AddonUpdatesPage<>(modManager, result)); + Controllers.navigateForward(new AddonUpdatesPage(modManager.getDirectory(), result)); } }) .withStagesHints("update.checking"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java index 2610043c634..74881fa0a62 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java @@ -54,6 +54,7 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.util.Lazy; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; @@ -76,6 +77,9 @@ public final class ResourcePackListPage extends ListPageBase implements VersionPage.GameInstanceLoadable { + public static final Lazy UNKNOWN_PACK_IMAGE = new Lazy<>(() -> + FXUtils.newBuiltinImage("/assets/img/unknown_pack.png", 64, 64, false, false)); + private static final String TIP_KEY = "resourcePackWarning"; private static @Nullable String getWarning(ResourcePackFile.Compatibility compatibility) { return switch (compatibility) { @@ -155,7 +159,7 @@ public void addFiles(List files) { try { resourcePackManager.importResourcePack(file); } catch (Exception e) { - LOG.warning("Failed to add resource pack", e); + LOG.warning("Failed to add resource pack " + file, e); failures.add(file); } } @@ -172,7 +176,7 @@ public void addFiles(List files) { } - public void onAddFiles() { + private void onAddFiles() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(i18n("resourcepack.add")); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("resourcepack"), "*.zip")); @@ -233,7 +237,7 @@ public void checkUpdates(Collection resourcePacks) { Runnable action = () -> Controllers.taskDialog(Task .composeAsync(() -> { Optional gameVersion = repository.getGameVersion(instanceId); - return gameVersion.map(g -> new AddonCheckUpdatesTask<>(DownloadProviders.getDownloadProvider(), g, resourcePacks)).orElse(null); + return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, resourcePacks)).orElse(null); }) .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception != null || result == null) { @@ -241,7 +245,7 @@ public void checkUpdates(Collection resourcePacks) { } else if (result.isEmpty()) { Controllers.dialog(i18n("addon.check_update.empty")); } else { - Controllers.navigateForward(new AddonUpdatesPage<>(resourcePackManager, result)); + Controllers.navigateForward(new AddonUpdatesPage(resourcePackDirectory, result)); } }) .withStagesHints("update.checking"), @@ -459,7 +463,7 @@ Image getIcon() { Image image = file.getIcon(); if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 || (Math.abs(image.getWidth() - image.getHeight()) >= 1)) { - image = FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"); + image = UNKNOWN_PACK_IMAGE.get(); } return image; } @@ -608,7 +612,7 @@ private static final class ResourcePackInfoDialog extends JFXDialogLayout { RemoteAddonRepository repository = item.getValue(); JFXHyperlink button = new JFXHyperlink(i18n(item.getKey())); Task.runAsync(() -> { - Optional versionOptional = repository.getRemoteVersionByLocalFile(packInfoObject.getFile().getFile()); + Optional versionOptional = repository.getRemoteVersionByLocalFile(pack.getFile()); if (versionOptional.isPresent()) { RemoteAddon remoteAddon = repository.getModById(DownloadProviders.getDownloadProvider(), versionOptional.get().modid()); FXUtils.runInFX(() -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java index fddcc0739b1..a09e8109bab 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java @@ -17,5 +17,518 @@ */ package org.jackhuang.hmcl.ui.versions; -public class ShaderListPage { +import com.jfoenix.controls.*; +import javafx.animation.PauseTransition; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import javafx.util.Duration; +import org.jackhuang.hmcl.addon.RemoteAddon; +import org.jackhuang.hmcl.addon.RemoteAddonRepository; +import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository; +import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository; +import org.jackhuang.hmcl.addon.shader.ShaderFile; +import org.jackhuang.hmcl.game.HMCLGameRepository; +import org.jackhuang.hmcl.setting.DownloadProviders; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.ListPageBase; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.animation.ContainerAnimations; +import org.jackhuang.hmcl.ui.animation.TransitionPane; +import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.TaskCancellationAction; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; + +import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent; +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; +import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; +import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public class ShaderListPage extends ListPageBase implements VersionPage.GameInstanceLoadable { + + private final ReentrantLock lock = new ReentrantLock(); + + private Path shadersDir; + private HMCLGameRepository repository; + private String instanceId; + + public ShaderListPage() { + FXUtils.applyDragListener(this, ShaderFile::isFileShaderPack, this::addFiles); + } + + @Override + protected Skin createDefaultSkin() { + return new ShaderListPageSkin(this); + } + + @Override + public void loadInstance(HMCLGameRepository repository, @Nullable String instanceId) { + this.repository = repository; + this.instanceId = instanceId; + this.shadersDir = repository.getShadersDirectory(instanceId); + refresh(); + } + + public void refresh() { + if (shadersDir == null) return; + setLoading(true); + Task.supplyAsync(Schedulers.io(), () -> { + lock.lock(); + try (var stream = Files.list(shadersDir)) { + return stream.map(file -> { + try { + return ShaderFile.fromFile(file); + } catch (IOException e) { + LOG.warning("Failed to load shader pack " + file, e); + return null; + } + }).filter(Objects::nonNull).toList(); + } finally { + lock.unlock(); + } + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + getItems().setAll(result); + } else { + LOG.warning("Failed to load shader packs", exception); + getItems().clear(); + } + setLoading(false); + }).start(); + } + + public void addFiles(List files) { + if (shadersDir == null) return; + List failures = new ArrayList<>(); + Task.runAsync(() -> { + lock.lock(); + try { + for (Path file : files) { + if (ShaderFile.isFileShaderPack(file)) { + try { + FileUtils.copyTo(file, shadersDir); + } catch (Exception e) { + LOG.warning("Failed to add shader pack " + file, e); + failures.add(file); + } + } else { + LOG.warning("Failed to add shader pack", new IllegalArgumentException("File '" + file + "' is not a shader pack")); + failures.add(file); + } + } + } finally { + lock.unlock(); + } + }).withRunAsync(Schedulers.javafx(), () -> { + if (!failures.isEmpty()) { + StringBuilder failure = new StringBuilder(i18n("shaderpack.add.failed")); + for (Path file: failures) { + failure.append("\n").append(file.toString()); + } + Controllers.dialog(failure.toString(), i18n("message.error"), MessageDialogPane.MessageType.ERROR); + } + refresh(); + }).start(); + } + + public void removeFiles(List selectedItems) { + try { + for (var shader : selectedItems) { + shader.delete(); + } + } catch (IOException e) { + Controllers.dialog(i18n("shaderpack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR); + LOG.warning("Failed to delete shader packs", e); + } + refresh(); + } + + public void checkUpdates(Collection shaderPacks) { + if (shadersDir == null) return; + Runnable action = () -> Controllers.taskDialog(Task + .composeAsync(() -> { + Optional gameVersion = repository.getGameVersion(instanceId); + return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, shaderPacks)).orElse(null); + }) + .whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception != null || result == null) { + Controllers.dialog(i18n("addon.check_update.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR); + } else if (result.isEmpty()) { + Controllers.dialog(i18n("addon.check_update.empty")); + } else { + Controllers.navigateForward(new AddonUpdatesPage(shadersDir, result)); + } + }) + .withStagesHints("update.checking"), + i18n("addon.check_update"), TaskCancellationAction.NORMAL); + + if (repository.isModpack(instanceId)) { + Controllers.confirm( + i18n("resourcepack.update_in_modpack.warning"), null, + MessageDialogPane.MessageType.WARNING, + action, null); + } else { + action.run(); + } + } + + private void onAddFiles() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle(i18n("shaderpack.add")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("shaderpack"), "*.zip")); + List files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage())); + if (files != null && !files.isEmpty()) { + addFiles(files); + } + } + + private void onDownload() { + Controllers.getDownloadPage().showShaderDownloads().selectVersion(instanceId); + Controllers.navigate(Controllers.getDownloadPage()); + } + + private void onOpenFolder() { + if (shadersDir != null) { + FXUtils.openFolder(shadersDir); + } + } + + private static Image getOrCreateIcon(ShaderFile shaderFile) { + Image image = shaderFile.getIcon(); + if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 || + (Math.abs(image.getWidth() - image.getHeight()) >= 1)) { + image = switch (shaderFile.getLoaderType()) { + case OPTIFINE_IRIS -> FXUtils.newBuiltinImage("/assets/img/opti-iris.png"); + default -> ResourcePackListPage.UNKNOWN_PACK_IMAGE.get(); + }; + } + return image; + } + + private static final class ShaderListPageSkin extends SkinBase { + private final JFXListView listView; + private final JFXTextField searchField = new JFXTextField(); + + private final TransitionPane toolbarPane = new TransitionPane(); + private final HBox searchBar = new HBox(); + private final HBox toolbarNormal = new HBox(); + private final HBox toolbarSelecting = new HBox(); + + private boolean isSearching; + + public ShaderListPageSkin(ShaderListPage control) { + super(control); + + StackPane pane = new StackPane(); + pane.setPadding(new Insets(10)); + pane.getStyleClass().addAll("notice-pane"); + + ComponentList root = new ComponentList(); + root.getStyleClass().add("no-padding"); + + listView = new JFXListView<>(); + + { + + // Toolbar Selecting + toolbarSelecting.getChildren().setAll( + createToolbarButton2(i18n("button.remove"), SVG.DELETE_FOREVER, () -> { + Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> + control.removeFiles(listView.getSelectionModel().getSelectedItems()), + null); + }), + createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () -> + control.checkUpdates(listView.getSelectionModel().getSelectedItems().stream().toList()) + ), + createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () -> + listView.getSelectionModel().selectAll()), + createToolbarButton2(i18n("button.cancel"), SVG.CANCEL, () -> + listView.getSelectionModel().clearSelection()) + ); + + // Search Bar + searchBar.setAlignment(Pos.CENTER); + searchBar.setPadding(new Insets(0, 5, 0, 5)); + searchField.setPromptText(i18n("search")); + HBox.setHgrow(searchField, Priority.ALWAYS); + PauseTransition pause = new PauseTransition(Duration.millis(100)); + pause.setOnFinished(e -> search()); + FXUtils.onChange(searchField.textProperty(), newValue -> { + pause.setRate(1); + pause.playFromStart(); + }); + + JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, + () -> { + changeToolbar(toolbarNormal); + + isSearching = false; + searchField.clear(); + Bindings.bindContent(listView.getItems(), getSkinnable().getItems()); + }); + + onEscPressed(searchField, closeSearchBar::fire); + + searchBar.getChildren().setAll(searchField, closeSearchBar); + + // Toolbar Normal + toolbarNormal.setAlignment(Pos.CENTER_LEFT); + toolbarNormal.setPickOnBounds(false); + toolbarNormal.getChildren().setAll( + createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, control::refresh), + createToolbarButton2(i18n("resourcepack.add"), SVG.ADD, control::onAddFiles), + createToolbarButton2(i18n("button.reveal_dir"), SVG.FOLDER_OPEN, control::onOpenFolder), + createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () -> + control.checkUpdates(listView.getItems().stream().toList()) + ), + createToolbarButton2(i18n("download"), SVG.DOWNLOAD, control::onDownload), + createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)) + ); + + FXUtils.onChangeAndOperate(listView.getSelectionModel().selectedItemProperty(), + selectedItem -> { + if (selectedItem == null) + changeToolbar(isSearching ? searchBar : toolbarNormal); + else + changeToolbar(toolbarSelecting); + }); + FXUtils.setOverflowHidden(toolbarPane, 8); + root.getContent().add(toolbarPane); + + // Clear selection when pressing ESC + root.addEventHandler(KeyEvent.KEY_PRESSED, e -> { + if (e.getCode() == KeyCode.ESCAPE) { + if (listView.getSelectionModel().getSelectedItem() != null) { + listView.getSelectionModel().clearSelection(); + e.consume(); + } + } + }); + } + + { + SpinnerPane center = new SpinnerPane(); + ComponentList.setVgrow(center, Priority.ALWAYS); + center.loadingProperty().bind(control.loadingProperty()); + + listView.setCellFactory(x -> new ShaderListCell(listView, control)); + listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + Bindings.bindContent(listView.getItems(), control.getItems()); + + listView.setOnContextMenuRequested(event -> { + ShaderFile selectedItem = listView.getSelectionModel().getSelectedItem(); + if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) { + listView.getSelectionModel().clearSelection(); + Controllers.dialog(new ShaderInfoDialog(control, selectedItem)); + } + }); + + ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); + listView.getStyleClass().add("no-horizontal-scrollbar"); + + center.setContent(listView); + root.getContent().add(center); + } + + pane.getChildren().setAll(root); + getChildren().setAll(pane); + } + + private void changeToolbar(HBox newToolbar) { + Node oldToolbar = toolbarPane.getCurrentNode(); + if (newToolbar != oldToolbar) { + toolbarPane.setContent(newToolbar, ContainerAnimations.FADE); + if (newToolbar == searchBar) { + Platform.runLater(searchField::requestFocus); + } + } + } + + private void search() { + isSearching = true; + + Bindings.unbindContent(listView.getItems(), getSkinnable().getItems()); + + String queryString = searchField.getText(); + if (StringUtils.isBlank(queryString)) { + listView.getItems().setAll(getSkinnable().getItems()); + } else { + listView.getItems().clear(); + + Predicate<@Nullable String> predicate; + try { + predicate = StringUtils.compileQuery(queryString); + } catch (Throwable e) { + LOG.warning("Illegal regular expression", e); + return; + } + + // Do we need to search in the background thread? + for (ShaderFile item : getSkinnable().getItems()) { + if (predicate.test(item.getFile().getFileName().toString()) + || predicate.test(item.getFileName())) { + listView.getItems().add(item); + } + } + } + } + + } + + private static final class ShaderListCell extends MDListCell { + + private final ShaderListPage page; + + private final ImageContainer imageContainer = new ImageContainer(24); + private final TwoLineListItem content = new TwoLineListItem(); + private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER); + private final JFXButton btnInfo = FXUtils.newToggleButton4(SVG.INFO); + + public ShaderListCell(JFXListView listView, ShaderListPage page) { + super(listView); + this.page = page; + + HBox root = new HBox(8); + root.setPickOnBounds(false); + root.setAlignment(Pos.CENTER_LEFT); + + HBox.setHgrow(content, Priority.ALWAYS); + content.setMouseTransparent(true); + + root.getChildren().setAll(imageContainer, content, btnReveal, btnInfo); + + setSelectable(); + + StackPane.setMargin(root, new Insets(8)); + getContainer().getChildren().add(root); + } + + @Override + protected void updateControl(ShaderFile item, boolean empty) { + if (empty || item == null) return; + + imageContainer.setImage(getOrCreateIcon(item)); + + content.getTags().clear(); + content.setTitle(item.getFileName()); + { + var apertureMeta = item.getApertureMeta(); + content.setSubtitle(apertureMeta != null ? apertureMeta.name() + " " + apertureMeta.version() : ""); + } + + FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager")); + btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(item.getFile())); + + btnInfo.setOnAction(e -> Controllers.dialog(new ShaderInfoDialog(this.page, item))); + } + } + + private static final class ShaderInfoDialog extends JFXDialogLayout { + + public ShaderInfoDialog(ShaderListPage page, ShaderFile shaderFile) { + + HBox titleContainer = new HBox(); + titleContainer.setSpacing(8); + + Stage stage = Controllers.getStage(); + maxWidthProperty().bind(stage.widthProperty().multiply(0.7)); + + ImageContainer imageContainer = new ImageContainer(40); + imageContainer.setImage(getOrCreateIcon(shaderFile)); + + TwoLineListItem title = new TwoLineListItem(); + title.setTitle(shaderFile.getFileName()); + title.setSubtitle(shaderFile.getFile().getFileName().toString()); + title.addTag(switch (shaderFile.getLoaderType()) { + case OPTIFINE_IRIS -> i18n("shaderpack.loader.optifine_iris"); + case APERTURE -> i18n("shaderpack.loader.aperture"); + }); + + titleContainer.getChildren().setAll(imageContainer, title); + setHeading(titleContainer); + + Label description = new Label(shaderFile.getApertureMeta() == null ? "" + : shaderFile.getApertureMeta().name() + " " + shaderFile.getApertureMeta().version()); + description.setWrapText(true); + FXUtils.copyOnDoubleClick(description); + + ScrollPane descriptionPane = new ScrollPane(description); + FXUtils.smoothScrolling(descriptionPane); + descriptionPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + descriptionPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + descriptionPane.setFitToWidth(true); + FXUtils.onChange(description.heightProperty(), newVal -> { + double maxHeight = stage.getHeight() * 0.5; + double targetHeight = Math.min(newVal.doubleValue(), maxHeight); + descriptionPane.setPrefViewportHeight(targetHeight); + }); + + setBody(descriptionPane); + + for (Pair item : Arrays.asList( + pair("addon.curseforge", CurseForgeRemoteAddonRepository.SHADERS), + pair("addon.modrinth", ModrinthRemoteAddonRepository.SHADER_PACKS) + )) { + RemoteAddonRepository repository = item.getValue(); + JFXHyperlink button = new JFXHyperlink(i18n(item.getKey())); + Task.runAsync(() -> { + Optional versionOptional = repository.getRemoteVersionByLocalFile(shaderFile.getFile()); + if (versionOptional.isPresent()) { + RemoteAddon remoteAddon = repository.getModById(DownloadProviders.getDownloadProvider(), versionOptional.get().modid()); + FXUtils.runInFX(() -> { + button.setOnAction(e -> { + fireEvent(new DialogCloseEvent()); + Controllers.navigate(new DownloadPage( + repository instanceof CurseForgeRemoteAddonRepository + ? HMCLLocalizedDownloadListPage.ofCurseForgeShaderPack(false) + : HMCLLocalizedDownloadListPage.ofModrinthShaderPack(false), + remoteAddon, + new HMCLGameRepository.InstanceReference(page.repository, page.instanceId), + null + )); + }); + button.setDisable(false); + }); + } + }).start(); + button.setDisable(true); + getActions().add(button); + } + + JFXButton okButton = new JFXButton(); + okButton.getStyleClass().add("dialog-accept"); + okButton.setText(i18n("button.ok")); + okButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); + getActions().add(okButton); + + onEscPressed(this, okButton::fire); + } + + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index 860c7bf6c78..d49fe45bcfa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -62,6 +62,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage private final TabHeader.Tab worldListTab = new TabHeader.Tab<>("worldList"); private final TabHeader.Tab schematicsTab = new TabHeader.Tab<>("schematicsTab"); private final TabHeader.Tab resourcePackTab = new TabHeader.Tab<>("resourcePackTab"); + private final TabHeader.Tab shaderListTab = new TabHeader.Tab<>("shaderListTab"); private final TransitionPane transitionPane = new TransitionPane(); private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty(); private final ObjectProperty instanceReference = new SimpleObjectProperty<>(); @@ -82,10 +83,11 @@ public VersionPage() { installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new)); modListTab.setNodeSupplier(loadVersionFor(ModListPage::new)); resourcePackTab.setNodeSupplier(loadVersionFor(ResourcePackListPage::new)); + shaderListTab.setNodeSupplier(loadVersionFor(ShaderListPage::new)); worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new)); schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new)); - tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab, resourcePackTab, worldListTab, schematicsTab); + tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab, resourcePackTab, shaderListTab, worldListTab, schematicsTab); tab.select(versionSettingsTab); addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); @@ -98,6 +100,8 @@ public VersionPage() { modListTab.getNode().loadInstance(getRepository(), getVersion()); if (resourcePackTab.isInitialized()) resourcePackTab.getNode().loadInstance(getRepository(), getVersion()); + if (shaderListTab.isInitialized()) + shaderListTab.getNode().loadInstance(getRepository(), getVersion()); if (worldListTab.isInitialized()) worldListTab.getNode().loadInstance(getRepository(), getVersion()); if (schematicsTab.isInitialized()) @@ -162,6 +166,8 @@ public void loadVersion(String version, HMCLGameRepository repository) { modListTab.getNode().loadInstance(repository, version); if (resourcePackTab.isInitialized()) resourcePackTab.getNode().loadInstance(repository, version); + if (shaderListTab.isInitialized()) + shaderListTab.getNode().loadInstance(repository, version); if (worldListTab.isInitialized()) worldListTab.getNode().loadInstance(repository, version); if (schematicsTab.isInitialized()) @@ -290,6 +296,7 @@ protected Skin(VersionPage control) { .addNavigationDrawerTab(control.tab, control.installerListTab, i18n("settings.tabs.installers"), SVG.DEPLOYED_CODE, SVG.DEPLOYED_CODE_FILL) .addNavigationDrawerTab(control.tab, control.modListTab, i18n("mods.manage"), SVG.EXTENSION, SVG.EXTENSION_FILL) .addNavigationDrawerTab(control.tab, control.resourcePackTab, i18n("resourcepack.manage"), SVG.TEXTURE) + .addNavigationDrawerTab(control.tab, control.shaderListTab, i18n("shaderpack.manage"), SVG.WB_SUNNY, SVG.WB_SUNNY_FILL) .addNavigationDrawerTab(control.tab, control.worldListTab, i18n("world.manage"), SVG.PUBLIC) .addNavigationDrawerTab(control.tab, control.schematicsTab, i18n("schematics.manage"), SVG.SCHEMA, SVG.SCHEMA_FILL); VBox.setVgrow(sideBar, Priority.ALWAYS); diff --git a/HMCL/src/main/resources/assets/img/opti-iris.png b/HMCL/src/main/resources/assets/img/opti-iris.png new file mode 100644 index 0000000000000000000000000000000000000000..9df5d2349b3d694467cd565dfec52c5c39eb7359 GIT binary patch literal 8143 zcmV;=A28sFP)00004XF*Lt006O% z3;baP000U}X+uL$b5ch_AW20-HZeIiHZ3wPF#rHaiJen-Sd;e_KHv9c4^~3h@UfR{ zfdC>StO&>uS)ve<0AYj>5AR{$W90N^4L=L-RlQUJ&DC0@ZjPh;=*jPLSYvv5M~MFBAl0-BNIsH z15C~g000{K(ZT*WKal6<?_01!^k@7iDG<<3=fuAC~28EsPoqkpK{9G%|Vj005J}`Hw&=0RYXHq~ibpyyzHQsFW8>#s~laM4*8xut5h5 z!4#~(4xGUqyucR%VFpA%3?#rj5JCpzfE)^;7?wd9RKPme1hudO8lVxH;SjXJF*pt9 z;1XPc>u?taU>Kgl7`%oF1VP9M6Ja4bh!J9r*dopd7nzO(B4J20l7OTj>4+3jBE`sZ zqynizYLQ(?Bl0bB6giDtK>Co|$RIL`{EECsF_eL_Q3KQhbwIhO9~z3rpmWi5G!I>X zmZEFX8nhlgfVQHi(M#xcbO3#dj$?q)F%D*o*1Pf{>6$SWH+$s3q(pv=X`qR|$iJF~TPzlc-O$C3+J1 z#CT#lv5;6stS0Uu9wDA3UMCI{Uz12A4#|?_P6{CkNG+sOq(0IRX`DyT~9-sA|ffUF>wk++Z!kWZ5P$;0Hg6gtI-;!FvmBvPc55=u2?Kjj3apE5$3psG>L zsh-pbs)#zDT1jo7c2F-(3)vyY4>O^>2$gY-Gd%Qm(Z8e zYv>2*=jns=cMJ`N4THx>VkjAF8G9M07`GWOnM|ey)0dgZR4~^v8<}UA514ONSSt1^ zd=-((5|uiYR+WC0=c-gyb5%dpd8!Lkt5pxHURHgkMpd&=fR^vEcAI*_=wwAG2sV%zY%w@v@XU~7=xdm1xY6*0;iwVIXu6TaXrs|dqbIl~ z?uTdNHFy_3W~^@g_pF#!K2~{F^;XxcN!DEJEbDF7 zS8PxlSDOr*I-AS3sI8l=#CDr)-xT5$k15hA^;2%zG3@;83hbKf2JJcaVfH2VZT8O{ z%p4LO);n}Nd~$Sk%yw*Wyz8XlG{dRHsl(}4XB%gsbDi@w7p6;)%MzD%mlsoQr;4X; zpL)xc%+^yMd)ZNTI#eJ*$O)i@o$z8)e??LqN_gLa_%;TM>o2SC_ zkmoO6c3xRt`@J4dvz#WL)-Y|z+r(Soy~}%GIzByR`p)SCKE^%*pL(B%zNWq+-#xw~ ze%5}Oeh2)X`#bu}{g3#+;d$~F@lFL`0l@*~0lk45fwKc^10MvL1f>Tx1&sx}1}_Xg z6+#RN4Ot&@lW)Km@*DYMGu&q^n$Z=?2%QyL8~QNJCQKgI5srq>2;UHXZ>IT7>CCnW zh~P(Th`1kV8JQRPeH1AwGO8}>QM6NZadh`A)~w`N`)9q5@sFvDxjWlxwsLl7tZHmh zY-8-3xPZ8-xPf?w_(k!T5_A(J3GIpG#Ms0=iQ{tu=WLoYoaCBRmULsT<=mpV7v|~C z%bs^USv6UZd^m-e5|^?+<%1wXP%juy<)>~<9TW0|n}ttBzM_qyQL(qUN<5P0omQ3h zINdvaL;7fjPeygdGYL;pD|wL_lDQ-EO;$wK-mK5raoH_7l$?~Dqf!lNmb5F^Ft;eT zPi8AClMUo~=55LwlZVRpxOiFd;3B_8yA~shQx|tGF!j;$toK>JuS&gYLDkTP@C~gS@r~shUu{a>bfJ1` z^^VQ7&C1OKHDNXFTgC{M|V%fo{xK_dk6MK@9S!GZ*1JJzrV5xZBjOk z9!NTH<(q(S+MDf~ceQX@Dh|Ry<-sT4rhI$jQ0Sq~!`#Eo-%($2E^vo}is5J@NVEf|KK?WT&2;PCq@=ncR8z zO#GQ^T~S@VXG71PKNocFOt)Y6$@AXlk6rM*aP%VgV%sIRORYVwJx6|U{ozQjTW{-S z_si{9Jg#)~P3t?+@6&(!YQWWV*Z9{iU7vZq@5byKw{9lg9JnRA_4s!7?H6|n?o8ZW zdXIRo{Jz@#>IeD{>VLHUv1Pz*;P_y`V9&!@5AO~Mho1hF|I>%z(nrik)gwkDjgOrl z9~%uCz4Bzvli{bbrxVZ0epdf^>vOB;-~HnIOV3#R*zgPai_gEVd8zYq@2jb=I>#f& zAH2?aJ@Kaetf}CysIAWyUjJ zW_$blmV58%AK!a3-@KV|06aZL4o(P~!o-6=ju_n`(x7p||^<>u|a9>ZJZv;7PD<*!x8&Pa|O$CDi zF!P$}h5#VDm&s%V1E39~vYT!2fPw6Lrp@yDd~ohw;B`G_eI)fp5uvXq6Ssf`Tw_X{ z{>UGB&>L0xyV~4*Wb;Fe0U|2~IJWnX*L`KNZct8V_N!&FCbKm$=XK^yV8CH6+VsI- z?T+ z-^>}%u*F>D1#)wrw;q6^O{&*tX?qiYS^j3ufW=$-Kn$kd+?Q-}JQy3W9)L30>}(MX zTO0R(MH{AR``{r92`t(}v*56A`SoI@S+Y#~#_< zdH^av_~48o zk~d!ifXHDaYkB>A$;F<`#QC(F z@dt5bUXSJ8$-9-j%?k@cl-Q+)zWa`;8xM~^aV3@31d0u=Mq4_tok2Y^Mgj!tf#fy7 zoY^Ra%|P1#5CB-PutxOdDZc}W5G8h{6~FrVlP`S!#QB9ZYEe-*LZ^vkB5W5%%J$}F z9kp{G2Lk|aaE1t$5k(1Df`~Z**3KjWFsuy8eIv#|FCZgX1HWrXA3trtiaP|n>ZF>A$`@|%VEJJ zG4;T6<^+=v)^nZv{=IbeDX&-}+Bi6lMOZMex_c!oq;W!k0JsPHBqyRhR6B5Eaaa42 zrbwx=rrx>OE!a1{B091+c<8kE=sEY|DwRW1Eo@kjH3xEeAXiBZ1d=f%q}_K@eQ4Np z+k@Upg)mUZ9|+<&D3ms&Nruoy3YlJiQ@uKDx}EiVv4Hgu&R^@cZmzc*cCzkIcS_}` zx-4o}+=6d@`}W4mZWWKuc@LlRA3p8X+vKQq&C1&CHu+#a0l>`0aEk3LJxKCioPo^DulMkAwaA(e+iSsOOXO*y}$ z5>3zA89jSnx7k%6`_j<&PPk(gGq3`i%@+U!fi0p)G7|xvwsL$^2u-;JwP}+}i9%>} zO3kKp-QJ$|JV@0qQkfVz94EmZ!Y2-ft4Ha@r+DuY8NnR|t299hj91Ng)wCm8ZOMzv zve7{~Q+>QoE{V^3S^wE558()qnVytudmA=wL@ zdpqNS&jz;!Lr7AT&8OEp>Vh`kl_#09U} zLA99M#8*c(%-ISg|z^<%dUjsCMDl*hlWJAAZ4Vr`_L>#JLk%5OJf!L}|T)lNVgy+W;OmgwSS0 zE&wp1u5>y&>Ou*0Vi^>;9CI<;v;xKoW}`*iK{tXFAgkDb2u!eojYZMZ#bJE>p1tq; z`pu|;s`975G4t2QZk-u!6@C4ShV#nX>!nfIS**Z% zWL>aBEWt9v8lJ0{Te{UQ?io{GI33L{wf0ZT{E}|F0zKL?#~N76#xiZ}J{U{Ja)vzn zs+b_Mbngi>>~=r=&Fla6M~8L~wF+Lk)WFfb`ptKYqPxpYm9_!^bGBJ}HcAfKZQD)a zR$`9s;pt&HQet^e5AIUjcS&o_ zsReWM9{GX0yubL@XtBX#Wio5ML@Qv|YZY2c1QyIWZ@z@1>+dlQ)*ZA7-a+9TiAS(violMNc#2}_#t<;a`tSmGFHdmH$?~sKh z(j>SqY#F{VWke^6J5H2#6ilMn@WzH2GnB;T2H$zR^Mm&n4qPMBv|l={MFJs8APGli zL?y%%^Y%pt)O%j#GocehSg-}N9sJmA`kC40^bx>doAH^g;LV-7zV!tcq0)~y*PN_Q zZ)tr-^DFkQ+ug6er#Ms*-6n5+mJvD$l*BE2WWmSD1^epfx$nNyYjk;F6AxO$u)H2Y zeII&NY+tz7Ll(XK3bKrXhv5o7dhEI{&F`HmwzjiATLf1D4k58Y+Q$e|#DYu?fe3;S zRBPEpqZ-ncI{);n_0(2bD0}oEdIiVT8W1+aX59A+0LWSje3V^$?AYE9edFd`!!1u3 zySLr+eRicfqq zzW0%2_oyJ+z9Q?j*RfSdZj@QiIUrx^9i`5{d}GfCj@^=-R#&keKxbJ`><B!{zf7F%zwq7{eH4zh_Y0>Ett#dW7-tB zg0X3eDBlqq9^4x*lL+z)v9zfEyg)!&GS5;e!T6e8$}W^@Js zcH<}^EeAR|Ro(GKWqLeF0(G^x&y$eE+MfDW!*r~mfd_?s{hHlS_!ZSzG}j*Tb{-7c zD`qV+wr&38O}vVXA(AEy(&$um`c!2{pku{)yNtO;tVnc?x)jO9xGL?!*Ot#6tBs7x zF^>bWRR@R>R3!lA(Bz>Yzfir3EZ_awaLsI(rPNA7Nhk#&O=6_hLTdpcX(BIe{9^ou zQ!=*I!PpQ0v?C}9-+{Km+AA+TD!cK} zkI(p*Yw4m-MuP=yY)q9U7bQR1R?P+Rg5_wm6&G4bBxPmVNm_jN{`T1)H0u`GGVwVm+NX^rn~G z^3In|mTq|X>-U`g@^^!YVOnXS6d=*uO{BGYsJQUbqe;Q9-hAE21E-z1OLd}B%+?u5 zodir3QCl|u_N1Qv2V(|)DcT@pypk#Wwbw#tt}8TR}0(-od^E2dG7eqrP)OIR2Wm4 z>AZdpu(r@-E!kcxpV=@Qh9Nc<*1}rL##+OWrd3WGfA{#^pM3nUrV4YRYBGsv#hHHo zNTq7q?N%ChNA~Rm%3J093IIIkI-T0f!(|>W z3S+@pwFFYuvZr;yb61FX!=WTebd6|h>xkfd$8VdF9AkmyiCD)4C{hMA8*f zHfFmchf24;>pBLfUA+!Yh^!I`c)f+ptWy(=7ONBGG}Z%%he%sEZ9NSM5(Z2#EMiMp zbB3Pg^`0#bvL3(`0Fls<>MkuFyle6LJJpq+Y8^pHfKAsQJv8y$_w03Dr(KUI_v}&< zb*%21R8l@#*ce-#EZ46zt+68itdoQkk(@_L)xf>XR+2%ll6Ay}r=nY~Kvr92lwJ3> zGDD<^A9q#`zj5iNH@k65T1(OcQ`%~zot49H-(9H|+l`nu-oqi$(n;H-iZ&oc9c#bj z7l%R}r+L10r0hoi;&QrLjqE6VV+zugm8m+m?AW3MnM2RdWCeQdDr>zYnw{8xu`=wP zJI1gOE|HRcF>MzLWAI8Q&Yo4T*TV#YNAgPAZJBgPc^mXHASoOv)Gjv$_^K6IUkYWs zGf7}NlthRPB}OKU6zWQ3i`zn;twIPS*|<76H-7E;>V$LQezqw{W_ZR#yyLmQbHiPS zl2*g5R5GK5l)_PLELlz@osSxFAaWTHWGuo;pekXSXa@i@DeuWJaXdP)V+_S zUXVq=TEe#_#jf0L5eBhP&-yezKeB6fc&DGnQiXkl%wV)?mkLvh!v{xJPR{#7V}7-) zlSFr;?s7d^t_2Ct?kdlTxFDUpQb++$+1Vw((Ji;T^=^uCXkBF;>8>=%SgB3%^hKk{ zloYUO<8B{9@@S=!`%03Hm&UUryJkmrduc2;;CliQ2^p<4CvMs=T*by(Z5rq1n{$^t zmzLOA8^xW))rA1M;7HwnrmqA%MYBtEc2Rl`I&nI;fOGWZ9I%q*_eMf!B)0esGes`4t>cShGHjXGeEm zxT^I4nAsnxte&1-_`%NrzHtA4ubi4|&R;QctOEbBFMfxb%f+|8%+}jfona-G9a(kMB9$!&E=w(#&geL?$?Pyf~+=9;Ct1QbRFuW1Cj#8Ks?jksoV`J!#IImI0C{WRUKrWEFgo*0t?!eNlD0hO zh2U~1PhJq`7R`$fq1N#}^hKIm=IPH;_@ha@o_L+Va`X54aU~a+;wd?=@002ovPDHLkV1kuy&wT&@ literal 0 HcmV?d00001 diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index a273ea6a6be..387de4116d0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -393,7 +393,6 @@ datetime.format=MMM d, yyyy, h\:mm\:ss a download=Download download.code.404=File "%s" not found on the remote server. download.content=Addons -download.shader=Shaders download.curseforge.unavailable=This HMCL build does not support access to CurseForge. Please use the official build to access CurseForge. download.existing=The file cannot be saved because it already exists. You can click "Save As" to save the file elsewhere. download.external_link=Visit Download Website @@ -1638,7 +1637,14 @@ settings.type.global.preset.remove=Delete Preset settings.type.global.preset.remove.confirm=Are you sure you want to delete preset "%s"? Instances using this preset will fall back to the default preset. settings.type.global.preset.auto_name=Preset %d +shaderpack=Shaders +shaderpack.add=Add +shaderpack.add.failed=Failed to add shader packs: +shaderpack.delete.failed=Failed to delete shader pack shaderpack.download.title=Download Shader - %1s +shaderpack.loader.aperture=Aperture +shaderpack.loader.optifine_iris=Optifine/Iris +shaderpack.manage=Shaders sponsor=Donors sponsor.bmclapi=Downloads for the Chinese Mainland are provided by BMCLAPI. Click here for more information. diff --git a/HMCL/src/main/resources/assets/lang/I18N_ar.properties b/HMCL/src/main/resources/assets/lang/I18N_ar.properties index 86f1c55edb3..7f082dfdce5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -367,7 +367,6 @@ datetime.format=MMM d, yyyy, h\:mm\:ss a download=تنزيل download.code.404=الملف "%s" غير موجود على الخادم البعيد. download.content=الإضافات -download.shader=الظلال download.curseforge.unavailable=هذا الإصدار من HMCL لا يدعم الوصول إلى CurseForge. يُرجى استخدام الإصدار الرسمي للوصول إلى CurseForge. download.existing=لا يمكن حفظ الملف لأنه موجود مسبقاً. يمكنك النقر على "حفظ باسم" لحفظ الملف في مكان آخر. download.external_link=زيارة موقع التنزيل @@ -1444,6 +1443,7 @@ settings.take_effect_after_restart=يُطبَّق بعد إعادة التشغي settings.type=نوع إعدادات النسخة settings.type.global.manage=الإعدادات العامة +shaderpack=الظلال shaderpack.download.title=تنزيل ظلال - %1s sponsor=المتبرعون diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 5fe0f296725..fc66bbb4364 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -347,7 +347,6 @@ datetime.format=d MMM yyyy, H\:mm\:ss download=Descargar download.code.404=Archivo no encontrado en el servidor remoto: %s download.content=Complementos -download.shader=Sombreadores download.curseforge.unavailable=Esta versión de HMCL no permite acceder a CurseForge. Utilice la versión oficial para acceder a CurseForge. download.existing=El archivo no se puede guardar porque ya existe. Puedes hacer clic en «Guardar como» para guardar el archivo en otro lugar. download.external_link=Abrir sitio web @@ -1344,6 +1343,8 @@ settings.take_effect_after_restart=Se aplica después de reiniciar settings.type=Tipo de configuración de la instancia settings.type.global.manage=Config. Global +shaderpack=Sombreadores + sponsor=Donantes sponsor.bmclapi=Las descargas de China continental son proporcionadas por BMCLAPI. Haga clic aquí para obtener más información. sponsor.hmcl=Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Haga clic aquí para obtener más información. diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index e8c7856aa8e..6dc4c0a5b5e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -299,7 +299,6 @@ datetime.format=yyyy/MM/dd H:mm:ss download=ダウンロード download.code.404=リモートサーバーにファイルが見つかりません:%s download.content=ゲームコンテンツ -download.shader=シェーダー download.existing=ファイルは既に存在するため、保存できません。「名前を付けて保存」を選択して、ファイルを別の場所に保存できます。 download.external_link=ダウンロードサイトを開く download.failed=%1$s のダウンロードに失敗しました、応答コード:%2$d @@ -871,6 +870,8 @@ settings.take_effect_after_restart=再起動後に有効になります settings.type=バージョン設定タイプ settings.type.global.manage=グローバルゲーム設定 +shaderpack=シェーダー + sponsor=寄付 sponsor.bmclapi=中国本土向けのダウンロードサービスはBMCLAPIによって提供されています。詳細については、ここをクリックしてください。 sponsor.hmcl=Hello Minecraft! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。詳細については、ここをクリックしてください。 diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index 54f23cbedb7..354e74bede4 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -342,7 +342,6 @@ datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss download=引 download.code.404=伺服器無案曰:%s\n君可求助於右上之鈕。 download.content=戲案 -download.shader=光影 download.curseforge.unavailable=HMCL 是版不能訪 CurseForge。宜行官版以取之。 download.existing=是案既存,無可貯之。宜移案他處。 download.external_link=啟引之網葉 @@ -1167,6 +1166,7 @@ settings.take_effect_after_restart=復啟而效 settings.type=戲例置設之類 settings.type.global.manage=全例戲設 +shaderpack=光影 shaderpack.download.title=引光影 - %1s sponsor=資勉 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index d9650eed3c1..7dca9be3e7a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -344,7 +344,6 @@ datetime.format=d MMM yyyy г., HH\:mm\:ss download=Скачать download.code.404=Файл «%s» не найден на удаленном сервере. download.content=Аддоны -download.shader=Шейдеры download.curseforge.unavailable=Эта сборка HMCL не поддерживает доступ к CurseForge. Пожалуйста, используйте официальную сборку для доступа к CurseForge. download.existing=Файл существует и по этому не может быть сохранён. Можно использовать «Сохранить как», чтобы сохранить файл в другом месте. download.external_link=Открыть сайт @@ -1333,6 +1332,8 @@ settings.take_effect_after_restart=Применится после переза settings.type=Тип настроек экземпляра settings.type.global.manage=Глобальные настройки +shaderpack=Шейдеры + sponsor=Спонсоры sponsor.bmclapi=Загрузки для материкового Китая предоставляются BMCLAPI. Нажмите здесь для получения подробностей. sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Для получения подробностей нажмите здесь. diff --git a/HMCL/src/main/resources/assets/lang/I18N_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index e105726b94d..c0a1fb40cc6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -343,7 +343,6 @@ datetime.format=dd.MM.yyyy, HH:mm:ss download=Завантажити download.code.404=Файл "%s" не знайдено на віддаленому сервері. download.content=Додатки -download.shader=Шейдери download.curseforge.unavailable=Ця збірка HMCL не підтримує доступ до CurseForge. Використовуйте офіційну збірку для доступу до CurseForge. download.existing=Файл не може бути збережений, оскільки він вже існує. Ви можете натиснути "Зберегти як", щоб зберегти файл в іншому місці. download.external_link=Відвідати веб-сайт завантаження @@ -1282,6 +1281,8 @@ settings.take_effect_after_restart=Застосовується після пе settings.type=Тип налаштувань екземпляра settings.type.global.manage=Глобальні налаштування +shaderpack=Шейдери + sponsor=Спонсори sponsor.bmclapi=Завантаження для материкового Китаю надається BMCLAPI. Натисніть тут для отримання додаткової інформації. sponsor.hmcl=Hello Minecraft! Лаунчер - це FOSS лаунчер Minecraft, який дозволяє користувачам легко керувати кількома екземплярами Minecraft. Натисніть тут для отримання додаткової інформації. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index b84437fc10b..bfb4f71af1b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -391,7 +391,6 @@ datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss download=下載 download.code.404=遠端伺服器沒有需要下載的檔案:%s download.content=遊戲內容 -download.shader=光影 download.curseforge.unavailable=這個 HMCL 版本不支援訪問 CurseForge。請使用官方版本進行下載。 download.existing=檔案已存在,無法儲存。你可以將檔案儲存至其他地方。 download.external_link=開啟下載網站 @@ -1429,6 +1428,7 @@ settings.type.global.preset.remove=刪除預設 settings.type.global.preset.remove.confirm=確定要刪除預設「%s」嗎?使用該預設的實例將改用預設項目。 settings.type.global.preset.auto_name=預設 %d +shaderpack=光影 shaderpack.download.title=光影下載 - %1s sponsor=贊助 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 bf37dad8f20..2e34fd9a24f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -393,7 +393,6 @@ datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss download=下载 download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。 download.content=游戏内容 -download.shader=光影 download.curseforge.unavailable=此 HMCL 版本不支持访问 CurseForge。请使用官方版本进行下载。 download.existing=文件已存在,无法保存。你可以将文件保存至其他地方。 download.external_link=打开下载网站 @@ -1434,6 +1433,7 @@ settings.type.global.preset.remove=删除预设 settings.type.global.preset.remove.confirm=确定要删除预设“%s”吗?使用该预设的实例将回退到默认预设。 settings.type.global.preset.auto_name=预设 %d +shaderpack=光影 shaderpack.download.title=光影下载 - %1s sponsor=赞助 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index e9f99a16fe9..be53fa38460 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -59,6 +59,9 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe return null; } + public void onUpdated(String newFileNameWithExtension) { + } + public record AddonUpdate( LocalAddonFile localAddonFile, RemoteAddon.Version currentVersion, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java index 43707fe5070..ca2fcffe32d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java @@ -344,11 +344,7 @@ public void importResourcePack(Path file) throws IOException, IllegalArgumentExc Files.createDirectories(resourcePackDirectory); Path newFile = resourcePackDirectory.resolve(file.getFileName()); - if (Files.isDirectory(file)) { - FileUtils.copyDirectory(file, newFile); - } else { - FileUtils.copyFile(file, newFile); - } + FileUtils.copyTo(file, resourcePackDirectory); addResourcePackInfo(newFile); } else { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java index d3503d35d01..36c053ec93c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java @@ -19,6 +19,8 @@ import org.jackhuang.hmcl.util.gson.JsonSerializable; +/// # STILL IN PREVIEW STATUS +/// [Aperture Example Pack](https://github.com/IrisShaders/Aperture-Example-Pack/blob/slang/shaders/pack.json) @JsonSerializable public record ApertureMeta(String name, String version) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java index c466df268a7..362785009e0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java @@ -20,6 +20,7 @@ import javafx.scene.image.Image; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.LocalAddonManager; +import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,12 +28,30 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.stream.Stream; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public sealed abstract class ShaderFile extends LocalAddonFile implements Comparable permits ShaderZipFile, ShaderFolder { public static @Nullable ShaderFile fromFile(Path file) throws IOException { return Files.isRegularFile(file) ? ShaderZipFile.load(file) : ShaderFolder.load(file); } + public static boolean isFileShaderPack(Path file) { + if (Files.isDirectory(file)) return Files.isDirectory(file.resolve("shaders")); + if (Files.isRegularFile(file)) { + try (var zipSystem = CompressingUtils.createReadOnlyZipFileSystem(file)) { + Path root = zipSystem.getRootDirectories().iterator().next(); + try (Stream stream = Files.walk(root)) { + return stream.filter(path -> path.endsWith("shaders")).anyMatch(Files::isDirectory); + } + } catch (IOException e) { + LOG.warning("Failed to check if file is shader pack", e); + } + } + return false; + } + protected Path file; protected final String fileNameWithoutExtension; protected final ShaderLoaderType loaderType; @@ -57,6 +76,18 @@ public String getFileName() { return fileNameWithoutExtension; } + public ShaderLoaderType getLoaderType() { + return loaderType; + } + + public @Nullable ApertureMeta getApertureMeta() { + return apertureMeta; + } + + public @Nullable Image getIcon() { + return icon; + } + @Override public boolean isDisabled() { return false; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index efda0ebe4ad..20837da192e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -45,11 +45,11 @@ final class ShaderZipFile extends ShaderFile { try (var zipSystem = CompressingUtils.createReadOnlyZipFileSystem(file)) { Path root = zipSystem.getRootDirectories().iterator().next(); try (Stream stream = Files.walk(root)) { - Path shadersPath = stream.filter(Files::isDirectory) + Path shadersPath = stream .filter(path -> path.endsWith("shaders")) + .filter(Files::isDirectory) .findFirst().orElse(null); if (shadersPath == null) return null; - if (!Files.isDirectory(shadersPath)) return null; if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture ApertureMeta meta = null; @@ -95,6 +95,22 @@ private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable Aperture .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) .toList(); if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), false); + return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), true); + } + + @Override + public void onUpdated(String newFileNameWithExtension) { + super.onUpdated(newFileNameWithExtension); + var configPath = getFile().resolveSibling(getFileName() + ".zip.txt"); + var newConfigPath = getFile().resolveSibling(newFileNameWithExtension + ".txt"); + if (Files.isRegularFile(configPath)) { + try { + Files.move(configPath, newConfigPath); + } catch (IOException e) { + LOG.warning("Failed to rename shader config file " + configPath, e); + } + } else { + LOG.warning("Failed to rename shader config file " + configPath + " because the file doesn't exist"); + } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index fb3f606081e..0d8669b8458 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -151,6 +151,11 @@ public Path getResourcePackDirectory(String id) { return getRunDirectory(id).resolve("resourcepacks"); } + @Override + public Path getShadersDirectory(String id) { + return getRunDirectory(id).resolve("shaderpacks"); + } + @Override public Path getVersionRoot(String id) { return getBaseDirectory().resolve("versions/" + id); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java index 1b7cc27dcd6..5e8377202f1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java @@ -143,6 +143,12 @@ default Task refreshVersionsAsync() { /// @return the resource pack directory Path getResourcePackDirectory(String id); + /// Get the directory for placing shader packs. + /// + /// @param id instance id + /// @return the shader pack directory + Path getShadersDirectory(String id); + /** * Get minecraft jar * diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java index e3268dc77c4..262e9290b12 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java @@ -491,6 +491,16 @@ public static void copyFile(Path srcFile, Path destFile) Files.copy(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); } + public static void copyTo(Path src, Path destDir) throws IOException { + Objects.requireNonNull(src, "Source must not be null"); + Objects.requireNonNull(destDir, "Destination directory must not be null"); + Path dest = destDir.resolve(src.getFileName()); + if (Files.isDirectory(src)) + copyDirectory(src, dest); + else + copyFile(src, dest); + } + public static List listFilesByExtension(Path file, String extension) { try (Stream list = Files.list(file)) { return list.filter(it -> Files.isRegularFile(it) && extension.equals(getExtension(it))) From d28ba7a72e713f7ed1b3118b154d19f4919cd4f0 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Mon, 6 Jul 2026 13:04:31 +0800 Subject: [PATCH 12/36] i18n --- .../jackhuang/hmcl/ui/versions/ResourcePackListPage.java | 2 +- .../org/jackhuang/hmcl/ui/versions/ShaderListPage.java | 2 +- HMCL/src/main/resources/assets/lang/I18N.properties | 1 + HMCL/src/main/resources/assets/lang/I18N_zh.properties | 7 +++++++ HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 7 +++++++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java index 74881fa0a62..0c362cfa8bb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java @@ -179,7 +179,7 @@ public void addFiles(List files) { private void onAddFiles() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(i18n("resourcepack.add")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("resourcepack"), "*.zip")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.resourcepack"), "*.zip")); List files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage())); if (files != null && !files.isEmpty()) { addFiles(files); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java index a09e8109bab..b1f662ed802 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java @@ -201,7 +201,7 @@ public void checkUpdates(Collection shaderPacks) { private void onAddFiles() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(i18n("shaderpack.add")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("shaderpack"), "*.zip")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.shaderpack"), "*.zip")); List files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage())); if (files != null && !files.isEmpty()) { addFiles(files); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 387de4116d0..0182147b547 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -443,6 +443,7 @@ extension.mod=Mod File extension.modloader.installer=Mod Loader Installer extension.resourcepack=Resource Pack Archive extension.schematic=Schematic File +extension.shaderpack=Shader Pack Archive extension.world=World Archive fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher cannot create the HMCL directory (%s). Please move HMCL to another location and reopen it. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index bfb4f71af1b..1dfcb2a430d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -437,6 +437,7 @@ extension.mod=模組檔案 extension.modloader.installer=模組載入器安裝檔 extension.resourcepack=資源包壓縮檔 extension.schematic=原理圖檔案 +extension.shaderpack=光影包壓縮包 extension.world=世界壓縮檔 fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 無法建立 HMCL 資料夾 (%s),請將 HMCL 移動至其他位置再開啟。 @@ -1429,7 +1430,13 @@ settings.type.global.preset.remove.confirm=確定要刪除預設「%s」嗎? settings.type.global.preset.auto_name=預設 %d shaderpack=光影 +shaderpack.add=新增光影包 +shaderpack.add.failed=新增光影包失敗: +shaderpack.delete.failed=刪除光影包失敗 shaderpack.download.title=光影下載 - %1s +shaderpack.loader.aperture=Aperture +shaderpack.loader.optifine_iris=Optifine/Iris +shaderpack.manage=光影包 sponsor=贊助 sponsor.bmclapi=中國大陸下載源由 BMCLAPI 提供高速下載服務。點選此處查閱詳細訊息。 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 2e34fd9a24f..d7b8786a32f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -440,6 +440,7 @@ extension.mod=模组文件 extension.modloader.installer=模组加载器安装包 extension.resourcepack=资源包压缩包 extension.schematic=原理图文件 +extension.shaderpack=光影包压缩包 extension.world=世界压缩包 fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 无法创建 HMCL 文件夹 (%s),请将 HMCL 移动至其他位置再启动。\n如遇到问题,你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 @@ -1434,7 +1435,13 @@ settings.type.global.preset.remove.confirm=确定要删除预设“%s”吗? settings.type.global.preset.auto_name=预设 %d shaderpack=光影 +shaderpack.add=添加光影包 +shaderpack.add.failed=添加光影包失败: +shaderpack.delete.failed=删除光影包失败 shaderpack.download.title=光影下载 - %1s +shaderpack.loader.aperture=Aperture +shaderpack.loader.optifine_iris=Optifine/Iris +shaderpack.manage=光影包 sponsor=赞助 sponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好地提供稳定高速的下载服务。[点击此处查阅详细信息] From a5108ca26cf5e4eecb350d4c53d2486ee25130ad Mon Sep 17 00:00:00 2001 From: ToobLac Date: Mon, 6 Jul 2026 13:06:05 +0800 Subject: [PATCH 13/36] i18n --- .../hmcl/ui/versions/ShaderListPage.java | 52 ++++++++----------- .../resources/assets/lang/I18N_zh.properties | 2 +- .../assets/lang/I18N_zh_CN.properties | 2 +- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java index b1f662ed802..5553b99eec3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java @@ -17,7 +17,10 @@ */ package org.jackhuang.hmcl.ui.versions; -import com.jfoenix.controls.*; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialogLayout; +import com.jfoenix.controls.JFXListView; +import com.jfoenix.controls.JFXTextField; import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.beans.binding.Bindings; @@ -53,6 +56,7 @@ import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; +import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; import org.jetbrains.annotations.Nullable; @@ -148,7 +152,7 @@ public void addFiles(List files) { }).withRunAsync(Schedulers.javafx(), () -> { if (!failures.isEmpty()) { StringBuilder failure = new StringBuilder(i18n("shaderpack.add.failed")); - for (Path file: failures) { + for (Path file : failures) { failure.append("\n").append(file.toString()); } Controllers.dialog(failure.toString(), i18n("message.error"), MessageDialogPane.MessageType.ERROR); @@ -171,31 +175,21 @@ public void removeFiles(List selectedItems) { public void checkUpdates(Collection shaderPacks) { if (shadersDir == null) return; - Runnable action = () -> Controllers.taskDialog(Task - .composeAsync(() -> { - Optional gameVersion = repository.getGameVersion(instanceId); - return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, shaderPacks)).orElse(null); - }) - .whenComplete(Schedulers.javafx(), (result, exception) -> { - if (exception != null || result == null) { - Controllers.dialog(i18n("addon.check_update.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR); - } else if (result.isEmpty()) { - Controllers.dialog(i18n("addon.check_update.empty")); - } else { - Controllers.navigateForward(new AddonUpdatesPage(shadersDir, result)); - } - }) - .withStagesHints("update.checking"), - i18n("addon.check_update"), TaskCancellationAction.NORMAL); - - if (repository.isModpack(instanceId)) { - Controllers.confirm( - i18n("resourcepack.update_in_modpack.warning"), null, - MessageDialogPane.MessageType.WARNING, - action, null); - } else { - action.run(); - } + + Controllers.taskDialog( + Task.composeAsync(() -> { + Optional gameVersion = repository.getGameVersion(instanceId); + return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, shaderPacks)).orElse(null); + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception != null || result == null) { + Controllers.dialog(I18n.i18n("addon.check_update.failed_check"), I18n.i18n("message.failed"), MessageDialogPane.MessageType.ERROR); + } else if (result.isEmpty()) { + Controllers.dialog(I18n.i18n("addon.check_update.empty")); + } else { + Controllers.navigateForward(new AddonUpdatesPage(shadersDir, result)); + } + }).withStagesHints("update.checking"), + I18n.i18n("addon.check_update"), TaskCancellationAction.NORMAL); } private void onAddFiles() { @@ -260,7 +254,7 @@ public ShaderListPageSkin(ShaderListPage control) { toolbarSelecting.getChildren().setAll( createToolbarButton2(i18n("button.remove"), SVG.DELETE_FOREVER, () -> { Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> - control.removeFiles(listView.getSelectionModel().getSelectedItems()), + control.removeFiles(listView.getSelectionModel().getSelectedItems()), null); }), createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () -> @@ -302,7 +296,7 @@ public ShaderListPageSkin(ShaderListPage control) { toolbarNormal.setPickOnBounds(false); toolbarNormal.getChildren().setAll( createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, control::refresh), - createToolbarButton2(i18n("resourcepack.add"), SVG.ADD, control::onAddFiles), + createToolbarButton2(i18n("shaderpack.add"), SVG.ADD, control::onAddFiles), createToolbarButton2(i18n("button.reveal_dir"), SVG.FOLDER_OPEN, control::onOpenFolder), createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () -> control.checkUpdates(listView.getItems().stream().toList()) diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 1dfcb2a430d..c3aada374f5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1436,7 +1436,7 @@ shaderpack.delete.failed=刪除光影包失敗 shaderpack.download.title=光影下載 - %1s shaderpack.loader.aperture=Aperture shaderpack.loader.optifine_iris=Optifine/Iris -shaderpack.manage=光影包 +shaderpack.manage=光影包管理 sponsor=贊助 sponsor.bmclapi=中國大陸下載源由 BMCLAPI 提供高速下載服務。點選此處查閱詳細訊息。 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 d7b8786a32f..b8137104010 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1441,7 +1441,7 @@ shaderpack.delete.failed=删除光影包失败 shaderpack.download.title=光影下载 - %1s shaderpack.loader.aperture=Aperture shaderpack.loader.optifine_iris=Optifine/Iris -shaderpack.manage=光影包 +shaderpack.manage=光影包管理 sponsor=赞助 sponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好地提供稳定高速的下载服务。[点击此处查阅详细信息] From 64346972eee27fa0b439977664698e62cd12b9e8 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Mon, 6 Jul 2026 13:38:48 +0800 Subject: [PATCH 14/36] meta --- .../hmcl/ui/versions/ShaderListPage.java | 20 +++++--- .../hmcl/addon/shader/ShaderFile.java | 15 ++++-- .../hmcl/addon/shader/ShaderFolder.java | 46 +++++++++---------- ...{ApertureMeta.java => ShaderPackMeta.java} | 7 +-- .../hmcl/addon/shader/ShaderZipFile.java | 46 +++++++++---------- 5 files changed, 73 insertions(+), 61 deletions(-) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/{ApertureMeta.java => ShaderPackMeta.java} (75%) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java index 5553b99eec3..6dfa00e41ed 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ShaderListPage.java @@ -385,8 +385,10 @@ private void search() { // Do we need to search in the background thread? for (ShaderFile item : getSkinnable().getItems()) { + var meta = item.getMeta(); if (predicate.test(item.getFile().getFileName().toString()) - || predicate.test(item.getFileName())) { + || predicate.test(item.getName()) + || (meta != null && (predicate.test(meta.version()) || predicate.test(meta.description())))) { listView.getItems().add(item); } } @@ -431,10 +433,13 @@ protected void updateControl(ShaderFile item, boolean empty) { content.getTags().clear(); content.setTitle(item.getFileName()); - { - var apertureMeta = item.getApertureMeta(); - content.setSubtitle(apertureMeta != null ? apertureMeta.name() + " " + apertureMeta.version() : ""); - } + content.setSubtitle(item.getMeta() == null || item.getMeta().name() == null ? "" : item.getMeta().name()); + content.addTag(switch (item.getLoaderType()) { + case OPTIFINE_IRIS -> i18n("shaderpack.loader.optifine_iris"); + case APERTURE -> i18n("shaderpack.loader.aperture"); + }); + if (item.getMeta() != null && item.getMeta().version() != null) + content.addTag(item.getMeta().version()); FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager")); btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(item.getFile())); @@ -467,8 +472,9 @@ public ShaderInfoDialog(ShaderListPage page, ShaderFile shaderFile) { titleContainer.getChildren().setAll(imageContainer, title); setHeading(titleContainer); - Label description = new Label(shaderFile.getApertureMeta() == null ? "" - : shaderFile.getApertureMeta().name() + " " + shaderFile.getApertureMeta().version()); + Label description = new Label(); + if (shaderFile.getMeta() != null && shaderFile.getMeta().description() != null) + description.setText(shaderFile.getMeta().description()); description.setWrapText(true); FXUtils.copyOnDoubleClick(description); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java index 362785009e0..b0a2a0ddaa0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import java.util.stream.Stream; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -55,14 +56,14 @@ public static boolean isFileShaderPack(Path file) { protected Path file; protected final String fileNameWithoutExtension; protected final ShaderLoaderType loaderType; - protected final @Nullable ApertureMeta apertureMeta; + protected final @Nullable ShaderPackMeta shaderPackMeta; protected final @Nullable Image icon; - protected ShaderFile(Path file, ShaderLoaderType loaderType, @Nullable ApertureMeta apertureMeta, @Nullable Image icon) { + protected ShaderFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta, @Nullable Image icon) { this.file = file; this.fileNameWithoutExtension = FileUtils.getNameWithoutExtension(file); this.loaderType = loaderType; - this.apertureMeta = apertureMeta; + this.shaderPackMeta = shaderPackMeta; this.icon = icon; } @@ -80,14 +81,18 @@ public ShaderLoaderType getLoaderType() { return loaderType; } - public @Nullable ApertureMeta getApertureMeta() { - return apertureMeta; + public @Nullable ShaderPackMeta getMeta() { + return shaderPackMeta; } public @Nullable Image getIcon() { return icon; } + public @NotNull String getName() { + return Optional.ofNullable(getMeta()).map(ShaderPackMeta::name).orElse(getFile().getFileName().toString()); + } + @Override public boolean isDisabled() { return false; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java index 560918e33c0..d2a6ff62d6d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java @@ -34,33 +34,33 @@ final class ShaderFolder extends ShaderFile { Path shadersPath = file.resolve("shaders"); if (!Files.isDirectory(shadersPath)) return null; - if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture - ApertureMeta meta = null; - try { - meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ApertureMeta.class); - } catch (IOException e) { - LOG.warning("Failed to load aperture shader metadata", e); - } - byte[] iconData = null; - Image icon = null; - try { - iconData = Files.readAllBytes(shadersPath.resolve("pack.png")); - } catch (IOException e) { - LOG.warning("Failed to read aperture shader icon", e); - } - if (iconData != null) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { - icon = new Image(inputStream, 64, 64, true, true); - } catch (Exception e) { - LOG.warning("Failed to load aperture shader icon", e); - } + ShaderPackMeta meta = null; + try { + meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ShaderPackMeta.class); + } catch (IOException e) { + LOG.warning("Failed to load shader metadata", e); + } + byte[] iconData = null; + Image icon = null; + try { + iconData = Files.readAllBytes(shadersPath.resolve("pack.png")); + } catch (IOException e) { + LOG.warning("Failed to read aperture shader icon", e); + } + if (iconData != null) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { + icon = new Image(inputStream, 64, 64, true, true); + } catch (Exception e) { + LOG.warning("Failed to load aperture shader icon", e); } + } + if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture return new ShaderFolder(file, ShaderLoaderType.APERTURE, meta, icon); } - return new ShaderFolder(file, ShaderLoaderType.OPTIFINE_IRIS, null, null); + return new ShaderFolder(file, ShaderLoaderType.OPTIFINE_IRIS, meta, icon); } - private ShaderFolder(Path file, ShaderLoaderType loaderType, @Nullable ApertureMeta apertureMeta, @Nullable Image icon) { - super(file, loaderType, apertureMeta, icon); + private ShaderFolder(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta, @Nullable Image icon) { + super(file, loaderType, shaderPackMeta, icon); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java similarity index 75% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java index 36c053ec93c..d2eed08de1c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ApertureMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java @@ -17,10 +17,11 @@ */ package org.jackhuang.hmcl.addon.shader; +import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.util.gson.JsonSerializable; +import org.jetbrains.annotations.Nullable; -/// # STILL IN PREVIEW STATUS -/// [Aperture Example Pack](https://github.com/IrisShaders/Aperture-Example-Pack/blob/slang/shaders/pack.json) @JsonSerializable -public record ApertureMeta(String name, String version) { +public record ShaderPackMeta(@Nullable String name, @Nullable String version, + @SerializedName(value = "shaderDescription") @Nullable String description) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index 20837da192e..a9597bb7449 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -51,36 +51,36 @@ final class ShaderZipFile extends ShaderFile { .findFirst().orElse(null); if (shadersPath == null) return null; - if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture - ApertureMeta meta = null; - try { - meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ApertureMeta.class); - } catch (IOException e) { - LOG.warning("Failed to load aperture shader metadata", e); - } - byte[] iconData = null; - Image icon = null; - try { - iconData = Files.readAllBytes(shadersPath.resolve("pack.png")); - } catch (IOException e) { - LOG.warning("Failed to read aperture shader icon", e); - } - if (iconData != null) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { - icon = new Image(inputStream, 64, 64, true, true); - } catch (Exception e) { - LOG.warning("Failed to load aperture shader icon", e); - } + ShaderPackMeta meta = null; + try { + meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ShaderPackMeta.class); + } catch (IOException e) { + LOG.warning("Failed to load aperture shader metadata", e); + } + byte[] iconData = null; + Image icon = null; + try { + iconData = Files.readAllBytes(shadersPath.resolve("pack.png")); + } catch (IOException e) { + LOG.warning("Failed to read aperture shader icon", e); + } + if (iconData != null) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { + icon = new Image(inputStream, 64, 64, true, true); + } catch (Exception e) { + LOG.warning("Failed to load aperture shader icon", e); } + } + if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture return new ShaderZipFile(file, ShaderLoaderType.APERTURE, meta, icon); } - return new ShaderZipFile(file, ShaderLoaderType.OPTIFINE_IRIS, null, null); + return new ShaderZipFile(file, ShaderLoaderType.OPTIFINE_IRIS, meta, icon); } } } - private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ApertureMeta apertureMeta, @Nullable Image icon) { - super(file, loaderType, apertureMeta, icon); + private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta, @Nullable Image icon) { + super(file, loaderType, shaderPackMeta, icon); } @Override From a60802b575ed8ce1eed38f7c51e5d3b369ceff57 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 16 Jul 2026 12:29:48 +0800 Subject: [PATCH 15/36] record --- .../game/LocalizedRemoteAddonRepository.java | 8 +- .../hmcl/ui/versions/ModListPageSkin.java | 4 +- .../hmcl/ui/versions/ModTranslations.java | 85 +++++-------------- .../ui/versions/ResourcePackListPage.java | 2 +- .../jackhuang/hmcl/addon/LocalAddonFile.java | 40 +++------ 5 files changed, 37 insertions(+), 102 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteAddonRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteAddonRepository.java index 3c531b0dd6a..24d75df0e35 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteAddonRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteAddonRepository.java @@ -50,7 +50,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion int count = 0; for (ModTranslations.Mod mod : ModTranslations.getTranslationsByRepositoryType(getType()).searchMod(searchFilter)) { - String englishSearchFilter = String.join(" ", StringUtils.tokenize(StringUtils.isNotBlank(mod.getSubname()) ? mod.getSubname() : mod.getName())); + String englishSearchFilter = String.join(" ", StringUtils.tokenize(StringUtils.isNotBlank(mod.subname()) ? mod.subname() : mod.name())); if (StringUtils.isNotBlank(englishSearchFilter)) { englishSearchFiltersSet.add(englishSearchFilter); } @@ -83,7 +83,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion } ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteAddon.slug()); - if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) { + if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.name()) && StringUtils.containsChinese(chineseTranslation.name())) { searchResultArray[chineseIndex++] = remoteAddon; } else { searchResultArray[englishIndex--] = remoteAddon; @@ -95,10 +95,10 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator(); return new SearchResult(Stream.concat(Arrays.stream(searchResultArray, 0, chineseIndex).map(remoteMod -> { ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.slug()); - if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) { + if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.name()) || !StringUtils.containsChinese(chineseRemoteMod.name())) { return Pair.pair(remoteMod, Integer.MAX_VALUE); } - String chineseRemoteModName = chineseRemoteMod.getName(); + String chineseRemoteModName = chineseRemoteMod.name(); if (searchFilter.isEmpty() || chineseRemoteModName.isEmpty()) { return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length())); } 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 3760abbbc6b..36221609a68 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 @@ -485,7 +485,7 @@ final class ModInfoDialog extends JFXDialogLayout { getActions().add(officialPageButton); } - if (modInfo.getModTranslations() == null || StringUtils.isBlank(modInfo.getModTranslations().getMcmod())) { + if (modInfo.getModTranslations() == null || StringUtils.isBlank(modInfo.getModTranslations().mcmod())) { JFXHyperlink searchButton = new JFXHyperlink(i18n("mods.mcmod.search")); searchButton.setOnAction(e -> { fireEvent(new DialogCloseEvent()); @@ -576,7 +576,7 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { String displayName = modInfo.getName(); if (modTranslations != null && I18n.isUseChinese()) { - String chineseName = modTranslations.getName(); + String chineseName = modTranslations.name(); if (StringUtils.containsChinese(chineseName)) { if (StringUtils.containsEmoji(chineseName)) { StringBuilder builder = new StringBuilder(); 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 e7bb8328d46..64a986c7c6b 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 @@ -40,13 +40,13 @@ public enum ModTranslations { MOD("/assets/mod_data.txt") { @Override public String getMcmodUrl(Mod mod) { - return String.format("https://www.mcmod.cn/class/%s.html", mod.getMcmod()); + return String.format("https://www.mcmod.cn/class/%s.html", mod.mcmod()); } }, MODPACK("/assets/modpack_data.txt") { @Override public String getMcmodUrl(Mod mod) { - return String.format("https://www.mcmod.cn/modpack/%s.html", mod.getMcmod()); + return String.format("https://www.mcmod.cn/modpack/%s.html", mod.mcmod()); } }, EMPTY("") { @@ -118,7 +118,7 @@ private static String cleanSubname(String subname) { try (BufferedReader reader = new BufferedReader( new InputStreamReader( ModTranslations.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8))) { - return this.mods = reader.lines().filter(line -> !line.startsWith("#")).map(Mod::new).toList(); + return this.mods = reader.lines().filter(line -> !line.startsWith("#")).map(Mod::fromLine).toList(); } catch (Exception e) { LOG.warning("Failed to load " + resourceName, e); return this.mods = List.of(); @@ -138,7 +138,7 @@ private static String cleanSubname(String subname) { List mods = getMods(); modIdMap = new HashMap<>(mods.size()); for (Mod mod : mods) { - for (String id : mod.getModIds()) { + for (String id : mod.modIds()) { if (StringUtils.isNotBlank(id)) { modIdMap.putIfAbsent(id, mod); } @@ -162,7 +162,7 @@ private static String cleanSubname(String subname) { List mods = getMods(); for (Mod mod : mods) { - String subname = cleanSubname(mod.getSubname()); + String subname = cleanSubname(mod.subname()); if (StringUtils.isNotBlank(subname)) { subnameMap.putIfAbsent(subname, mod); } @@ -185,8 +185,8 @@ private static String cleanSubname(String subname) { List mods = getMods(); curseForgeMap = new HashMap<>(mods.size()); for (Mod mod : mods) { - if (StringUtils.isNotBlank(mod.getCurseforge())) { - curseForgeMap.putIfAbsent(mod.getCurseforge(), mod); + if (StringUtils.isNotBlank(mod.curseforge())) { + curseForgeMap.putIfAbsent(mod.curseforge(), mod); } } @@ -209,17 +209,17 @@ private static String cleanSubname(String subname) { keywords = new ArrayList<>(); int maxKeywordLength = -1; for (Mod mod : mods) { - if (StringUtils.isNotBlank(mod.getName())) { - keywords.add(pair(mod.getName(), mod)); - maxKeywordLength = Math.max(maxKeywordLength, mod.getName().length()); + if (StringUtils.isNotBlank(mod.name())) { + keywords.add(pair(mod.name(), mod)); + maxKeywordLength = Math.max(maxKeywordLength, mod.name().length()); } - if (StringUtils.isNotBlank(mod.getSubname())) { - keywords.add(pair(mod.getSubname(), mod)); - maxKeywordLength = Math.max(maxKeywordLength, mod.getSubname().length()); + if (StringUtils.isNotBlank(mod.subname())) { + keywords.add(pair(mod.subname(), mod)); + maxKeywordLength = Math.max(maxKeywordLength, mod.subname().length()); } - if (StringUtils.isNotBlank(mod.getAbbr())) { - keywords.add(pair(mod.getAbbr(), mod)); - maxKeywordLength = Math.max(maxKeywordLength, mod.getAbbr().length()); + if (StringUtils.isNotBlank(mod.abbr())) { + keywords.add(pair(mod.abbr(), mod)); + maxKeywordLength = Math.max(maxKeywordLength, mod.abbr().length()); } } @@ -250,7 +250,7 @@ public Mod getMod(String id, String subname) { subname = cleanSubname(subname); if (StringUtils.isNotBlank(subname)) { Mod mod = getSubnameMap().get(subname); - if (mod != null && (StringUtils.isBlank(id) || mod.getModIds().contains(id))) + if (mod != null && (StringUtils.isBlank(id) || mod.modIds().contains(id))) return mod; } @@ -282,35 +282,14 @@ public List searchMod(String query) { .toList(); } - public static final class Mod { - private final String curseforge; - private final String mcmod; - private final List modIds; - private final String name; - private final String subname; - private final String abbr; - - public Mod(String line) { + public record Mod(String curseforge, String mcmod, List modIds, String name, String subname, String abbr) { + public static Mod fromLine(String line) { String[] items = line.split(";", -1); if (items.length != 6) { throw new IllegalArgumentException("Illegal mod data line, 6 items expected " + line); } - curseforge = items[0]; - mcmod = items[1]; - modIds = List.of(items[2].split(",")); - name = items[3]; - subname = items[4]; - abbr = items[5]; - } - - public Mod(String curseforge, String mcmod, List modIds, String name, String subname, String abbr) { - this.curseforge = curseforge; - this.mcmod = mcmod; - this.modIds = modIds; - this.name = name; - this.subname = subname; - this.abbr = abbr; + return new Mod(items[0], items[1], List.of(items[2].split(",")), items[3], items[4], items[5]); } public String getDisplayName() { @@ -324,29 +303,5 @@ public String getDisplayName() { } return builder.toString(); } - - public String getCurseforge() { - return curseforge; - } - - public String getMcmod() { - return mcmod; - } - - public List getModIds() { - return modIds; - } - - public String getName() { - return name; - } - - public String getSubname() { - return subname; - } - - public String getAbbr() { - return abbr; - } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java index 37cce12845f..8d315dddef1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java @@ -431,7 +431,7 @@ private void search() { LocalAddonFile.Description description = resourcePack.getDescription(); Stream descriptionParts = description == null ? Stream.empty() - : description.getParts().stream().map(LocalAddonFile.Description.Part::getText); + : description.parts().stream().map(LocalAddonFile.Description.Part::text); if (predicate.test(resourcePack.getFileNameWithExtension()) || predicate.test(resourcePack.getFileName()) || descriptionParts.anyMatch(predicate)) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index be53fa38460..9b7512347af 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -70,26 +70,16 @@ public record AddonUpdate( ) { } - public static class Description { - private final List parts; - + public record Description(List parts) { public Description(String text) { - this.parts = new ArrayList<>(); - this.parts.add(new LocalAddonFile.Description.Part(text, "black")); - } - - public Description(List parts) { - this.parts = parts; - } - - public List getParts() { - return parts; + this(new ArrayList<>()); + this.parts.add(new Part(text, "black")); } @Override public String toString() { StringBuilder builder = new StringBuilder(); - for (LocalAddonFile.Description.Part part : parts) { + for (Part part : parts) { builder.append(part.text); } return builder.toString(); @@ -99,25 +89,15 @@ public String toStringSingleLine() { return toString().lines().map(String::trim).filter(StringUtils::isNotBlank).collect(Collectors.joining(" | ")); } - public static class Part { - private final String text; - private final String color; + public record Part(String text, String color) { - public Part(String text) { - this(text, ""); + public Part { + Objects.requireNonNull(text); + Objects.requireNonNull(color); } - public Part(String text, String color) { - this.text = Objects.requireNonNull(text); - this.color = Objects.requireNonNull(color); - } - - public String getText() { - return text; - } - - public String getColor() { - return color; + public Part(String text) { + this(text, ""); } } } From 7852910f159745954cff0970b9896b49c9ac08cf Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 16 Jul 2026 12:30:10 +0800 Subject: [PATCH 16/36] update --- .../src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 9b7512347af..56c95002848 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -20,6 +20,7 @@ import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -77,7 +78,7 @@ public Description(String text) { } @Override - public String toString() { + public @NotNull String toString() { StringBuilder builder = new StringBuilder(); for (Part part : parts) { builder.append(part.text); From a213da4b40bdf5eb487190bbd9db0945f4ceb77c Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 15:49:26 +0800 Subject: [PATCH 17/36] update --- .../hmcl/ui/versions/AddonUpdatesPage.java | 2 +- .../jackhuang/hmcl/addon/LocalAddonFile.java | 3 +- .../hmcl/addon/mod/LocalModFile.java | 2 +- .../resourcepack/ResourcePackManager.java | 30 +++++++++++++++++++ .../resourcepack/ResourcePackZipFile.java | 7 ++++- .../hmcl/addon/shader/ShaderZipFile.java | 2 +- 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java index 320a26e7f69..689f02d210f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java @@ -294,7 +294,7 @@ public static class AddonUpdateTask extends Task { RemoteAddon.Version remote = addon.targetVersion(); boolean isDisabled = local.isDisabled(); String originalFileName = local.getFile().getFileName().toString(); - String fileName = addon.useRemoteFileName() ? remote.file().filename() : originalFileName; + String fileName = remote.file().filename(); if (isDisabled) fileName = StringUtils.addSuffix(fileName, LocalAddonManager.DISABLED_EXTENSION); String newFileName = fileName; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 56c95002848..bb935fb12ae 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -66,8 +66,7 @@ public void onUpdated(String newFileNameWithExtension) { public record AddonUpdate( LocalAddonFile localAddonFile, RemoteAddon.Version currentVersion, - RemoteAddon.Version targetVersion, - boolean useRemoteFileName + RemoteAddon.Version targetVersion ) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index b7c10d28264..54c4cbc31d0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -201,7 +201,7 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) .toList(); if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), true); + return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0)); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java index d98bffabe23..a8f46799687 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java @@ -514,6 +514,36 @@ public Stream> arePacksEnabled(Stream options = loadOptions(); + List optPacks = new ArrayList<>(deserializePackList(options.get("resourcePacks"))); + List optIncompatiblePacks = new ArrayList<>(deserializePackList(options.get("incompatibleResourcePacks"))); + for (var l : List.of(optPacks, optIncompatiblePacks)) { + var it = l.listIterator(); + while (it.hasNext()) { + var next = it.next(); + if (oldName.equals(next) || oldNameNewFormat.equals(next)) { + it.set(newName); + modified = true; + } + } + } + if (modified) { + options.put("resourcePacks", serializePackList(optPacks)); + options.put("incompatibleResourcePacks", serializePackList(optIncompatiblePacks)); + saveOptions(options); + } + return modified; + } finally { + lock.unlock(); + } + } + public ResourcePackFile.Compatibility getCompatibility(@NotNull ResourcePackFile resourcePack) { if (resourcePack.getMeta().pack() == null) return ResourcePackFile.Compatibility.MISSING_PACK_META; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java index ffeabab2e0c..83ea26cca31 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java @@ -73,6 +73,11 @@ private ResourcePackZipFile(ResourcePackManager manager, Path path, PackMcMeta m super(manager, path, meta, icon); } + @Override + public void onUpdated(String newFileNameWithExtension) { + manager.rename(getFileNameWithExtension(), newFileNameWithExtension); + } + @Override public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.RESOURCE_PACK); @@ -85,7 +90,7 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) .toList(); if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), false); + return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index a9597bb7449..9b9d4f103f9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -95,7 +95,7 @@ private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPa .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) .toList(); if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), true); + return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0)); } @Override From fedb3585b111f53268287ce46059694d4d191253 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 15:55:42 +0800 Subject: [PATCH 18/36] update --- .../main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java | 2 +- .../hmcl/addon/pack/resourcepack/ResourcePackZipFile.java | 5 +++-- .../java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index bb935fb12ae..8c0fe563c17 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -60,7 +60,7 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe return null; } - public void onUpdated(String newFileNameWithExtension) { + public void onUpdated(String newFileNameWithExt) { } public record AddonUpdate( diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java index 83ea26cca31..233a418bbd1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java @@ -74,8 +74,9 @@ private ResourcePackZipFile(ResourcePackManager manager, Path path, PackMcMeta m } @Override - public void onUpdated(String newFileNameWithExtension) { - manager.rename(getFileNameWithExtension(), newFileNameWithExtension); + public void onUpdated(String newFileNameWithExt) { + super.onUpdated(newFileNameWithExt); + manager.rename(getFileNameWithExtension(), newFileNameWithExt); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index 9b9d4f103f9..a3038a5afb2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -99,10 +99,10 @@ private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPa } @Override - public void onUpdated(String newFileNameWithExtension) { - super.onUpdated(newFileNameWithExtension); + public void onUpdated(String newFileNameWithExt) { + super.onUpdated(newFileNameWithExt); var configPath = getFile().resolveSibling(getFileName() + ".zip.txt"); - var newConfigPath = getFile().resolveSibling(newFileNameWithExtension + ".txt"); + var newConfigPath = getFile().resolveSibling(newFileNameWithExt + ".txt"); if (Files.isRegularFile(configPath)) { try { Files.move(configPath, newConfigPath); From dfb4409a0362afa699b5bb23850f4693657c0a58 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 20:34:14 +0800 Subject: [PATCH 19/36] update --- .../hmcl/ui/instances/ShaderListPage.java | 16 +++++++++------- .../hmcl/game/DefaultGameRepository.java | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java index 6dfa00e41ed..f4641e153ac 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.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.ui.versions; +package org.jackhuang.hmcl.ui.instances; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialogLayout; @@ -42,6 +42,7 @@ import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository; import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository; import org.jackhuang.hmcl.addon.shader.ShaderFile; +import org.jackhuang.hmcl.game.GameInstanceID; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.setting.DownloadProviders; import org.jackhuang.hmcl.task.Schedulers; @@ -53,6 +54,7 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.instances.*; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; @@ -74,13 +76,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class ShaderListPage extends ListPageBase implements VersionPage.GameInstanceLoadable { +public class ShaderListPage extends ListPageBase implements GameInstancePage.GameInstanceLoadable { private final ReentrantLock lock = new ReentrantLock(); - private Path shadersDir; private HMCLGameRepository repository; - private String instanceId; + private @Nullable GameInstanceID instanceId; + private @Nullable Path shadersDir; public ShaderListPage() { FXUtils.applyDragListener(this, ShaderFile::isFileShaderPack, this::addFiles); @@ -92,10 +94,10 @@ protected Skin createDefaultSkin() { } @Override - public void loadInstance(HMCLGameRepository repository, @Nullable String instanceId) { + public void loadInstance(HMCLGameRepository repository, @Nullable GameInstanceID instanceId) { this.repository = repository; this.instanceId = instanceId; - this.shadersDir = repository.getShadersDirectory(instanceId); + this.shadersDir = instanceId != null ? repository.getShadersDirectory(instanceId) : null; refresh(); } @@ -203,7 +205,7 @@ private void onAddFiles() { } private void onDownload() { - Controllers.getDownloadPage().showShaderDownloads().selectVersion(instanceId); + Controllers.getDownloadPage().showShaderDownloads().selectInstance(instanceId); Controllers.navigate(Controllers.getDownloadPage()); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index e5d2097af5b..ebeb3927e3e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -19,7 +19,7 @@ import com.google.gson.JsonParseException; import org.jackhuang.hmcl.addon.mod.ModManager; -import org.jackhuang.hmcl.addon.resourcepack.ResourcePackManager; +import org.jackhuang.hmcl.addon.pack.resourcepack.ResourcePackManager; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.event.*; import org.jackhuang.hmcl.modpack.ModpackConfiguration; @@ -473,8 +473,8 @@ public Path getResourcePackDirectory(GameInstanceID instanceId) { } @Override - public Path getShadersDirectory(String id) { - return getRunDirectory(id).resolve("shaderpacks"); + public Path getShadersDirectory(GameInstanceID instanceId) { + return getRunDirectory(instanceId).resolve("shaderpacks"); } public Path getInstanceJson(GameInstanceID instanceId) { From abe1fee2c7de5053f94758d873e9191bb6e471a1 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 20:59:46 +0800 Subject: [PATCH 20/36] update --- .../java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java index f4641e153ac..265c2bc8204 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java @@ -54,7 +54,6 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.*; -import org.jackhuang.hmcl.ui.instances.*; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; From 3941a808a142ee7214d56d9ad5872ab020dede7f Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 21:53:09 +0800 Subject: [PATCH 21/36] update --- .../hmcl/addon/pack/resourcepack/ResourcePackManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java index f88c1dbe668..4eb657c69f5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java @@ -517,6 +517,7 @@ public Stream> arePacksEnabled(Stream Date: Sun, 2 Aug 2026 10:44:46 +0800 Subject: [PATCH 22/36] update --- .../java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java | 2 +- .../java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java index 265c2bc8204..ac90de7e84d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java @@ -501,7 +501,7 @@ public ShaderInfoDialog(ShaderListPage page, ShaderFile shaderFile) { Task.runAsync(() -> { Optional versionOptional = repository.getRemoteVersionByLocalFile(shaderFile.getFile()); if (versionOptional.isPresent()) { - RemoteAddon remoteAddon = repository.getModById(DownloadProviders.getDownloadProvider(), versionOptional.get().modid()); + RemoteAddon remoteAddon = repository.getAddonById(DownloadProviders.getDownloadProvider(), versionOptional.get().projectId()); FXUtils.runInFX(() -> { button.setOnAction(e -> { fireEvent(new DialogCloseEvent()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index a3038a5afb2..33a057c8dc2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -89,7 +89,7 @@ private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPa if (repository == null) return null; Optional currentVersion = repository.getRemoteVersionByLocalFile(file); if (currentVersion.isEmpty()) return null; - List remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().modid()) + List remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().projectId()) .filter(version -> version.gameVersions().contains(gameVersion)) //TODO loader .filter(version -> version.datePublished().compareTo(currentVersion.get().datePublished()) > 0) .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) From c8251a2ca76a6f6be94f1a9db465e9241a72b7ed Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 6 Aug 2026 11:59:02 +0800 Subject: [PATCH 23/36] update --- .../org/jackhuang/hmcl/ui/instances/DownloadPage.java | 9 +++++---- .../hmcl/ui/instances/HMCLLocalizedDownloadListPage.java | 4 ++-- .../org/jackhuang/hmcl/addon/shader/ShaderZipFile.java | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/DownloadPage.java index 6c235a11f90..7d9b4c08193 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/DownloadPage.java @@ -71,8 +71,9 @@ public class DownloadPage extends Control implements DecoratorPage { (downloadProvider, repository, version, shader, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(downloadProvider, repository, version, file, "shaderpacks"); - public static @Nullable DownloadCallback getDownloadCallbackFor(RemoteAddonRepository.Type repoType) { - return switch (repoType) { + public static @Nullable DownloadCallback getDownloadCallbackFor(@Nullable RemoteAddon.Type addonType) { + if (addonType == null) return null; + return switch (addonType) { case MOD -> FOR_MOD; case RESOURCE_PACK -> FOR_RESOURCE_PACK; case SHADER_PACK -> FOR_SHADER; @@ -178,8 +179,8 @@ public void setFailed(boolean failed) { } public @Nullable DownloadCallback getCallbackFor(RemoteAddon addon) { - if (addon.repoType() == RemoteAddonRepository.Type.MODPACK) return this.callback; - return getDownloadCallbackFor(addon.repoType()); + if (addon.type() == RemoteAddon.Type.MODPACK) return this.callback; + return getDownloadCallbackFor(addon.type()); } public void download(RemoteAddon addon, RemoteAddon.Version file) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/HMCLLocalizedDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/HMCLLocalizedDownloadListPage.java index 78fd491c36d..eae59b77bc1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/HMCLLocalizedDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/HMCLLocalizedDownloadListPage.java @@ -66,11 +66,11 @@ public static DownloadListPage ofShaderPack(boolean instanceSelection) { } public static DownloadListPage ofCurseForgeShaderPack(boolean instanceSelection) { - return new HMCLLocalizedDownloadListPage(null, instanceSelection, RemoteAddonRepository.Type.SHADER_PACK, CurseForgeRemoteAddonRepository.SHADERS, null); + return new HMCLLocalizedDownloadListPage(null, instanceSelection, RemoteAddon.Type.SHADER_PACK, CurseForgeRemoteAddonRepository.SHADERS, null); } public static DownloadListPage ofModrinthShaderPack(boolean instanceSelection) { - return new HMCLLocalizedDownloadListPage(null, instanceSelection, RemoteAddonRepository.Type.SHADER_PACK, null, ModrinthRemoteAddonRepository.SHADER_PACKS); + return new HMCLLocalizedDownloadListPage(null, instanceSelection, RemoteAddon.Type.SHADER_PACK, null, ModrinthRemoteAddonRepository.SHADER_PACKS); } private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean instanceSelection, RemoteAddon.Type type, CurseForgeRemoteAddonRepository curseForge, ModrinthRemoteAddonRepository modrinth) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java index 33a057c8dc2..d713badd1a8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java @@ -85,7 +85,7 @@ private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPa @Override public @Nullable AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { - RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.SHADER_PACK); + RemoteAddonRepository repository = source.getRepoForType(RemoteAddon.Type.SHADER_PACK); if (repository == null) return null; Optional currentVersion = repository.getRemoteVersionByLocalFile(file); if (currentVersion.isEmpty()) return null; @@ -95,7 +95,7 @@ private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPa .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) .toList(); if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0)); + return new AddonUpdate(source, RemoteAddon.Type.SHADER_PACK, this, currentVersion.get(), remoteVersions.get(0)); } @Override From c3111228124032ddd11b4671cd3bf675624e4dd1 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 19 Aug 2026 21:52:49 +0800 Subject: [PATCH 24/36] update --- .../ui/instances/HMCLLocalizedDownloadListPage.java | 8 -------- .../jackhuang/hmcl/ui/instances/ShaderListPage.java | 12 +----------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/HMCLLocalizedDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/HMCLLocalizedDownloadListPage.java index 3f3cd0970f3..ca44043f655 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/HMCLLocalizedDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/HMCLLocalizedDownloadListPage.java @@ -49,14 +49,6 @@ public static DownloadListPage ofShaderPack(boolean instanceSelection) { return page; } - public static DownloadListPage ofCurseForgeShaderPack(boolean instanceSelection) { - return new HMCLLocalizedDownloadListPage(null, instanceSelection, RemoteAddon.Type.SHADER_PACK, CurseForgeRemoteAddonRepository.SHADERS, null); - } - - public static DownloadListPage ofModrinthShaderPack(boolean instanceSelection) { - return new HMCLLocalizedDownloadListPage(null, instanceSelection, RemoteAddon.Type.SHADER_PACK, null, ModrinthRemoteAddonRepository.SHADER_PACKS); - } - private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean instanceSelection, RemoteAddon.Type type, CurseForgeRemoteAddonRepository curseForge, ModrinthRemoteAddonRepository modrinth) { super(null, callback, instanceSelection); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java index ac90de7e84d..4193a77c0bb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java @@ -503,17 +503,7 @@ public ShaderInfoDialog(ShaderListPage page, ShaderFile shaderFile) { if (versionOptional.isPresent()) { RemoteAddon remoteAddon = repository.getAddonById(DownloadProviders.getDownloadProvider(), versionOptional.get().projectId()); FXUtils.runInFX(() -> { - button.setOnAction(e -> { - fireEvent(new DialogCloseEvent()); - Controllers.navigate(new DownloadPage( - repository instanceof CurseForgeRemoteAddonRepository - ? HMCLLocalizedDownloadListPage.ofCurseForgeShaderPack(false) - : HMCLLocalizedDownloadListPage.ofModrinthShaderPack(false), - remoteAddon, - new HMCLGameRepository.InstanceReference(page.repository, page.instanceId), - null - )); - }); + button.setExternalLink(remoteAddon.pageUrl()); button.setDisable(false); }); } From 7fe795abdfafe14044e9a9babde112da2c56269c Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 20 Aug 2026 22:11:52 +0800 Subject: [PATCH 25/36] update --- .../hmcl/ui/instances/ModListPageSkin.java | 2 +- .../ui/instances/ResourcePackListPage.java | 13 ++-- .../hmcl/ui/instances/ShaderListPage.java | 73 +++++++++++-------- .../hmcl/game/DefaultGameInstance.java | 2 +- .../org/jackhuang/hmcl/game/GameInstance.java | 7 ++ 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPageSkin.java index 9539a61d958..e72f1c456f4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPageSkin.java @@ -394,7 +394,7 @@ public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference new ResourcePackListCell(listView, control)); + listView.setCellFactory(x -> new ResourcePackListCell(listView)); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); StackPane placeholderContainer = new StackPane(); @@ -437,7 +437,7 @@ private ResourcePackListPageSkin(ResourcePackListPage control) { ResourcePackInfoObject selectedItem = listView.getSelectionModel().getSelectedItem(); if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) { listView.getSelectionModel().clearSelection(); - Controllers.dialog(new ResourcePackInfoDialog(control, selectedItem)); + Controllers.dialog(new ResourcePackInfoDialog(selectedItem)); } }); @@ -528,8 +528,6 @@ Image getIcon() { private static final class ResourcePackListCell extends MDListCell { private static final PseudoClass WARNING = PseudoClass.getPseudoClass("warning"); - private final ResourcePackListPage page; - private final JFXCheckBox checkBox; private final ImageContainer imageContainer = new ImageContainer(32); private final TwoLineListItem content = new TwoLineListItem(); @@ -540,9 +538,8 @@ private static final class ResourcePackListCell extends MDListCell listView, ResourcePackListPage page) { + public ResourcePackListCell(JFXListView listView) { super(listView); - this.page = page; getStyleClass().add("resource-pack-list-cell"); @@ -602,7 +599,7 @@ protected void updateControl(ResourcePackInfoObject item, boolean empty) { FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager")); btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(file.getFile())); - btnInfo.setOnAction(e -> Controllers.dialog(new ResourcePackInfoDialog(this.page, item))); + btnInfo.setOnAction(e -> Controllers.dialog(new ResourcePackInfoDialog(item))); if (booleanProperty != null) { checkBox.selectedProperty().unbindBidirectional(booleanProperty); @@ -621,7 +618,7 @@ protected void updateControl(ResourcePackInfoObject item, boolean empty) { private static final class ResourcePackInfoDialog extends JFXDialogLayout { - ResourcePackInfoDialog(ResourcePackListPage page, ResourcePackInfoObject packInfoObject) { + ResourcePackInfoDialog(ResourcePackInfoObject packInfoObject) { ResourcePackFile pack = packInfoObject.getFile(); HBox titleContainer = new HBox(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java index 4193a77c0bb..86c004747dc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java @@ -24,6 +24,7 @@ import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableValue; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -35,22 +36,17 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.stage.FileChooser; -import javafx.stage.Stage; import javafx.util.Duration; import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository; import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository; import org.jackhuang.hmcl.addon.shader.ShaderFile; -import org.jackhuang.hmcl.game.GameInstanceID; -import org.jackhuang.hmcl.game.HMCLGameRepository; +import org.jackhuang.hmcl.game.HMCLGameInstance; import org.jackhuang.hmcl.setting.DownloadProviders; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.ListPageBase; -import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.*; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.*; @@ -59,6 +55,7 @@ import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -75,16 +72,24 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class ShaderListPage extends ListPageBase implements GameInstancePage.GameInstanceLoadable { +public class ShaderListPage extends ListPageBase { private final ReentrantLock lock = new ReentrantLock(); - private HMCLGameRepository repository; - private @Nullable GameInstanceID instanceId; + private final WeakListenerHolder listenerHolder = new WeakListenerHolder(); + private @Nullable HMCLGameInstance gameInstance; + private @Nullable Path shadersDir; - public ShaderListPage() { + public ShaderListPage(ObservableValue instanceContext) { + Objects.requireNonNull(instanceContext, "instanceContext"); FXUtils.applyDragListener(this, ShaderFile::isFileShaderPack, this::addFiles); + + listenerHolder.add(FXUtils.onWeakChangeAndOperate(instanceContext, current -> { + if (current != null) { + loadInstance(current); + } + })); } @Override @@ -92,11 +97,16 @@ protected Skin createDefaultSkin() { return new ShaderListPageSkin(this); } - @Override - public void loadInstance(HMCLGameRepository repository, @Nullable GameInstanceID instanceId) { - this.repository = repository; - this.instanceId = instanceId; - this.shadersDir = instanceId != null ? repository.getShadersDirectory(instanceId) : null; + public void loadInstance(HMCLGameInstance.Optional instance) { + this.gameInstance = instance.instance(); + if (gameInstance == null) { + this.shadersDir = null; + getItems().clear(); + return; + } + + this.shadersDir = gameInstance.getShadersDirectory(); + refresh(); } @@ -175,12 +185,16 @@ public void removeFiles(List selectedItems) { } public void checkUpdates(Collection shaderPacks) { - if (shadersDir == null) return; + HMCLGameInstance gameInstance = this.gameInstance; + if (gameInstance == null) return; Controllers.taskDialog( Task.composeAsync(() -> { - Optional gameVersion = repository.getGameVersion(instanceId); - return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, shaderPacks)).orElse(null); + GameVersionNumber version = gameInstance.getVersion(); + return version != GameVersionNumber.unknown() + ? new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), + version.toString(), shaderPacks) + : null; }).whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception != null || result == null) { Controllers.dialog(I18n.i18n("addon.check_update.failed_check"), I18n.i18n("message.failed"), MessageDialogPane.MessageType.ERROR); @@ -204,7 +218,8 @@ private void onAddFiles() { } private void onDownload() { - Controllers.getDownloadPage().showShaderDownloads().selectInstance(instanceId); + if (gameInstance == null) return; + Controllers.getDownloadPage().showShaderDownloads().selectInstance(gameInstance.getId()); Controllers.navigate(Controllers.getDownloadPage()); } @@ -332,7 +347,7 @@ public ShaderListPageSkin(ShaderListPage control) { ComponentList.setVgrow(center, Priority.ALWAYS); center.loadingProperty().bind(control.loadingProperty()); - listView.setCellFactory(x -> new ShaderListCell(listView, control)); + listView.setCellFactory(x -> new ShaderListCell(listView)); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); Bindings.bindContent(listView.getItems(), control.getItems()); @@ -340,7 +355,7 @@ public ShaderListPageSkin(ShaderListPage control) { ShaderFile selectedItem = listView.getSelectionModel().getSelectedItem(); if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) { listView.getSelectionModel().clearSelection(); - Controllers.dialog(new ShaderInfoDialog(control, selectedItem)); + Controllers.dialog(new ShaderInfoDialog(selectedItem)); } }); @@ -400,16 +415,13 @@ private void search() { private static final class ShaderListCell extends MDListCell { - private final ShaderListPage page; - private final ImageContainer imageContainer = new ImageContainer(24); private final TwoLineListItem content = new TwoLineListItem(); private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER); private final JFXButton btnInfo = FXUtils.newToggleButton4(SVG.INFO); - public ShaderListCell(JFXListView listView, ShaderListPage page) { + public ShaderListCell(JFXListView listView) { super(listView); - this.page = page; HBox root = new HBox(8); root.setPickOnBounds(false); @@ -445,19 +457,18 @@ protected void updateControl(ShaderFile item, boolean empty) { FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager")); btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(item.getFile())); - btnInfo.setOnAction(e -> Controllers.dialog(new ShaderInfoDialog(this.page, item))); + btnInfo.setOnAction(e -> Controllers.dialog(new ShaderInfoDialog(item))); } } private static final class ShaderInfoDialog extends JFXDialogLayout { - public ShaderInfoDialog(ShaderListPage page, ShaderFile shaderFile) { + public ShaderInfoDialog(ShaderFile shaderFile) { HBox titleContainer = new HBox(); titleContainer.setSpacing(8); - Stage stage = Controllers.getStage(); - maxWidthProperty().bind(stage.widthProperty().multiply(0.7)); + maxWidthProperty().bind(Controllers.getDecorator().contentWidthProperty().multiply(0.7)); ImageContainer imageContainer = new ImageContainer(40); imageContainer.setImage(getOrCreateIcon(shaderFile)); @@ -485,7 +496,7 @@ public ShaderInfoDialog(ShaderListPage page, ShaderFile shaderFile) { descriptionPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); descriptionPane.setFitToWidth(true); FXUtils.onChange(description.heightProperty(), newVal -> { - double maxHeight = stage.getHeight() * 0.5; + double maxHeight = Controllers.getDecorator().contentHeightProperty().get() * 0.5; double targetHeight = Math.min(newVal.doubleValue(), maxHeight); descriptionPane.setPrefViewportHeight(targetHeight); }); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java index 87a3a867424..58df20f457b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java @@ -19,7 +19,7 @@ import com.google.gson.JsonParseException; import org.jackhuang.hmcl.addon.mod.ModManager; -import org.jackhuang.hmcl.addon.resourcepack.ResourcePackManager; +import org.jackhuang.hmcl.addon.pack.resourcepack.ResourcePackManager; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameInstance.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameInstance.java index 85258c4b1f9..75d9259066f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameInstance.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameInstance.java @@ -149,6 +149,13 @@ default Path getResourcePackDirectory() { return getRunDirectory().resolve("resourcepacks"); } + /// Returns the directory containing shader packs used by this instance. + /// + /// @return the shader pack directory below the run directory + default Path getShadersDirectory() { + return getRunDirectory().resolve("shaderpacks"); + } + /// Returns the directory containing saved worlds used by this instance. /// /// @return the saves directory below the run directory From 28f36b0bf3c5faaed2221d4569929bacaaf3eb81 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 22 Aug 2026 10:26:49 +0800 Subject: [PATCH 26/36] update --- .../main/java/org/jackhuang/hmcl/game/GameRepository.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java index aca2ce978eb..e1741129ff0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java @@ -126,12 +126,6 @@ default Path getInstanceRoot(GameInstanceID instanceId) { return getLayout().getInstanceRoot(instanceId); } - /// Returns the shader pack directory for an instance. - /// - /// @param instanceId instance id - /// @return the shader pack directory - Path getShadersDirectory(GameInstanceID instanceId); - /// Returns the primary client jar path for a manifest. /// /// @param manifest the manifest whose jar should be located From a71c5b15a7ad5d2e36906158ed6a5227414ff93d Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 29 Aug 2026 20:22:01 +0800 Subject: [PATCH 27/36] update --- .../hmcl/ui/instances/GameInstancePage.java | 6 +- ...rListPage.java => ShaderPackListPage.java} | 122 ++++++++---------- .../{ShaderFile.java => ShaderPackFile.java} | 32 +++-- ...haderFolder.java => ShaderPackFolder.java} | 37 +++--- .../hmcl/addon/shader/ShaderPackManager.java | 111 ++++++++++++++++ ...derZipFile.java => ShaderPackZipFile.java} | 61 ++++----- .../hmcl/game/DefaultGameInstance.java | 18 +++ 7 files changed, 250 insertions(+), 137 deletions(-) rename HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/{ShaderListPage.java => ShaderPackListPage.java} (84%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/{ShaderFile.java => ShaderPackFile.java} (73%) rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/{ShaderFolder.java => ShaderPackFolder.java} (61%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackManager.java rename HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/{ShaderZipFile.java => ShaderPackZipFile.java} (66%) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameInstancePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameInstancePage.java index 5710fa035f7..4cea78de6b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameInstancePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameInstancePage.java @@ -60,8 +60,8 @@ public class GameInstancePage extends DecoratorAnimatedPage implements Decorator private final TabHeader.Tab worldListTab = new TabHeader.Tab<>("worldList"); private final TabHeader.Tab schematicsTab = new TabHeader.Tab<>("schematicsTab"); private final TabHeader.Tab resourcePackTab = new TabHeader.Tab<>("resourcePackTab"); - private final TabHeader.Tab shaderListTab = new TabHeader.Tab<>("shaderListTab"); - private final TransitionPane transitionPane = new TransitionPane(); + private final TabHeader.Tab shaderListTab = new TabHeader.Tab<>("shaderListTab"); + private final TransitionPane transitionPane = new TransitionPane(); private final BooleanProperty currentInstanceUpgradable = new SimpleBooleanProperty(); private final ObjectProperty instance = new SimpleObjectProperty<>(this, "instance"); @@ -91,7 +91,7 @@ public GameInstancePage() { installerListTab.setNodeSupplier(() -> new InstallerListPage(instance)); modListTab.setNodeSupplier(() -> new ModListPage(instance)); resourcePackTab.setNodeSupplier(() -> new ResourcePackListPage(instance)); - shaderListTab.setNodeSupplier(() -> new ShaderListPage(instance)); + shaderListTab.setNodeSupplier(() -> new ShaderPackListPage(instance)); worldListTab.setNodeSupplier(() -> new WorldListPage(instance)); schematicsTab.setNodeSupplier(() -> new SchematicsPage(instance)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java similarity index 84% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java index 86c004747dc..bd7062a3d51 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java @@ -41,7 +41,8 @@ import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository; import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository; -import org.jackhuang.hmcl.addon.shader.ShaderFile; +import org.jackhuang.hmcl.addon.shader.ShaderPackFile; +import org.jackhuang.hmcl.addon.shader.ShaderPackManager; import org.jackhuang.hmcl.game.HMCLGameInstance; import org.jackhuang.hmcl.setting.DownloadProviders; import org.jackhuang.hmcl.task.Schedulers; @@ -59,7 +60,6 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.locks.ReentrantLock; @@ -72,18 +72,19 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class ShaderListPage extends ListPageBase { +public class ShaderPackListPage extends ListPageBase { private final ReentrantLock lock = new ReentrantLock(); private final WeakListenerHolder listenerHolder = new WeakListenerHolder(); private @Nullable HMCLGameInstance gameInstance; - private @Nullable Path shadersDir; + private Path shaderPackDir; + private ShaderPackManager shaderPackManager; - public ShaderListPage(ObservableValue instanceContext) { + public ShaderPackListPage(ObservableValue instanceContext) { Objects.requireNonNull(instanceContext, "instanceContext"); - FXUtils.applyDragListener(this, ShaderFile::isFileShaderPack, this::addFiles); + FXUtils.applyDragListener(this, ShaderPackFile::isFileShaderPack, this::addFiles); listenerHolder.add(FXUtils.onWeakChangeAndOperate(instanceContext, current -> { if (current != null) { @@ -94,36 +95,32 @@ public ShaderListPage(ObservableValue insta @Override protected Skin createDefaultSkin() { - return new ShaderListPageSkin(this); + return new ShaderPackListPageSkin(this); } public void loadInstance(HMCLGameInstance.Optional instance) { this.gameInstance = instance.instance(); if (gameInstance == null) { - this.shadersDir = null; + this.shaderPackDir = null; + this.shaderPackManager = null; getItems().clear(); return; } - this.shadersDir = gameInstance.getShadersDirectory(); + this.shaderPackDir = gameInstance.getShadersDirectory(); + this.shaderPackManager = gameInstance.getShaderPackManager(); refresh(); } public void refresh() { - if (shadersDir == null) return; + if (shaderPackManager == null) return; setLoading(true); Task.supplyAsync(Schedulers.io(), () -> { lock.lock(); - try (var stream = Files.list(shadersDir)) { - return stream.map(file -> { - try { - return ShaderFile.fromFile(file); - } catch (IOException e) { - LOG.warning("Failed to load shader pack " + file, e); - return null; - } - }).filter(Objects::nonNull).toList(); + try { + shaderPackManager.refresh(); + return shaderPackManager.getLocalFiles(); } finally { lock.unlock(); } @@ -139,31 +136,22 @@ public void refresh() { } public void addFiles(List files) { - if (shadersDir == null) return; + if (shaderPackManager == null) return; + List failures = new ArrayList<>(); Task.runAsync(() -> { - lock.lock(); - try { - for (Path file : files) { - if (ShaderFile.isFileShaderPack(file)) { - try { - FileUtils.copyTo(file, shadersDir); - } catch (Exception e) { - LOG.warning("Failed to add shader pack " + file, e); - failures.add(file); - } - } else { - LOG.warning("Failed to add shader pack", new IllegalArgumentException("File '" + file + "' is not a shader pack")); - failures.add(file); - } + for (Path file : files) { + try { + shaderPackManager.importShaderPack(file); + } catch (Exception e) { + LOG.warning("Failed to add shader pack " + file, e); + failures.add(file); } - } finally { - lock.unlock(); } }).withRunAsync(Schedulers.javafx(), () -> { if (!failures.isEmpty()) { StringBuilder failure = new StringBuilder(i18n("shaderpack.add.failed")); - for (Path file : failures) { + for (Path file: failures) { failure.append("\n").append(file.toString()); } Controllers.dialog(failure.toString(), i18n("message.error"), MessageDialogPane.MessageType.ERROR); @@ -172,11 +160,9 @@ public void addFiles(List files) { }).start(); } - public void removeFiles(List selectedItems) { + public void removeFiles(List selectedItems) { try { - for (var shader : selectedItems) { - shader.delete(); - } + shaderPackManager.removeShaderPacks(selectedItems); } catch (IOException e) { Controllers.dialog(i18n("shaderpack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR); LOG.warning("Failed to delete shader packs", e); @@ -184,7 +170,7 @@ public void removeFiles(List selectedItems) { refresh(); } - public void checkUpdates(Collection shaderPacks) { + public void checkUpdates(Collection shaderPacks) { HMCLGameInstance gameInstance = this.gameInstance; if (gameInstance == null) return; @@ -201,7 +187,7 @@ public void checkUpdates(Collection shaderPacks) { } else if (result.isEmpty()) { Controllers.dialog(I18n.i18n("addon.check_update.empty")); } else { - Controllers.navigateForward(new AddonUpdatesPage(shadersDir, result)); + Controllers.navigateForward(new AddonUpdatesPage(shaderPackDir, result)); } }).withStagesHints("update.checking"), I18n.i18n("addon.check_update"), TaskCancellationAction.NORMAL); @@ -224,16 +210,16 @@ private void onDownload() { } private void onOpenFolder() { - if (shadersDir != null) { - FXUtils.openFolder(shadersDir); + if (shaderPackDir != null) { + FXUtils.openFolder(shaderPackDir); } } - private static Image getOrCreateIcon(ShaderFile shaderFile) { - Image image = shaderFile.getIcon(); + private static Image getOrCreateIcon(ShaderPackFile shaderPackFile) { + Image image = shaderPackFile.loadIcon(); if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 || (Math.abs(image.getWidth() - image.getHeight()) >= 1)) { - image = switch (shaderFile.getLoaderType()) { + image = switch (shaderPackFile.getLoaderType()) { case OPTIFINE_IRIS -> FXUtils.newBuiltinImage("/assets/img/opti-iris.png"); default -> ResourcePackListPage.UNKNOWN_PACK_IMAGE.get(); }; @@ -241,8 +227,8 @@ private static Image getOrCreateIcon(ShaderFile shaderFile) { return image; } - private static final class ShaderListPageSkin extends SkinBase { - private final JFXListView listView; + private static final class ShaderPackListPageSkin extends SkinBase { + private final JFXListView listView; private final JFXTextField searchField = new JFXTextField(); private final TransitionPane toolbarPane = new TransitionPane(); @@ -252,7 +238,7 @@ private static final class ShaderListPageSkin extends SkinBase { private boolean isSearching; - public ShaderListPageSkin(ShaderListPage control) { + public ShaderPackListPageSkin(ShaderPackListPage control) { super(control); StackPane pane = new StackPane(); @@ -347,15 +333,15 @@ public ShaderListPageSkin(ShaderListPage control) { ComponentList.setVgrow(center, Priority.ALWAYS); center.loadingProperty().bind(control.loadingProperty()); - listView.setCellFactory(x -> new ShaderListCell(listView)); + listView.setCellFactory(x -> new ShaderPackListCell(listView)); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); Bindings.bindContent(listView.getItems(), control.getItems()); listView.setOnContextMenuRequested(event -> { - ShaderFile selectedItem = listView.getSelectionModel().getSelectedItem(); + ShaderPackFile selectedItem = listView.getSelectionModel().getSelectedItem(); if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) { listView.getSelectionModel().clearSelection(); - Controllers.dialog(new ShaderInfoDialog(selectedItem)); + Controllers.dialog(new ShaderPackInfoDialog(selectedItem)); } }); @@ -400,7 +386,7 @@ private void search() { } // Do we need to search in the background thread? - for (ShaderFile item : getSkinnable().getItems()) { + for (ShaderPackFile item : getSkinnable().getItems()) { var meta = item.getMeta(); if (predicate.test(item.getFile().getFileName().toString()) || predicate.test(item.getName()) @@ -413,14 +399,14 @@ private void search() { } - private static final class ShaderListCell extends MDListCell { + private static final class ShaderPackListCell extends MDListCell { private final ImageContainer imageContainer = new ImageContainer(24); private final TwoLineListItem content = new TwoLineListItem(); private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER); private final JFXButton btnInfo = FXUtils.newToggleButton4(SVG.INFO); - public ShaderListCell(JFXListView listView) { + public ShaderPackListCell(JFXListView listView) { super(listView); HBox root = new HBox(8); @@ -439,7 +425,7 @@ public ShaderListCell(JFXListView listView) { } @Override - protected void updateControl(ShaderFile item, boolean empty) { + protected void updateControl(ShaderPackFile item, boolean empty) { if (empty || item == null) return; imageContainer.setImage(getOrCreateIcon(item)); @@ -457,13 +443,13 @@ protected void updateControl(ShaderFile item, boolean empty) { FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager")); btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(item.getFile())); - btnInfo.setOnAction(e -> Controllers.dialog(new ShaderInfoDialog(item))); + btnInfo.setOnAction(e -> Controllers.dialog(new ShaderPackInfoDialog(item))); } } - private static final class ShaderInfoDialog extends JFXDialogLayout { + private static final class ShaderPackInfoDialog extends JFXDialogLayout { - public ShaderInfoDialog(ShaderFile shaderFile) { + public ShaderPackInfoDialog(ShaderPackFile shaderPackFile) { HBox titleContainer = new HBox(); titleContainer.setSpacing(8); @@ -471,12 +457,12 @@ public ShaderInfoDialog(ShaderFile shaderFile) { maxWidthProperty().bind(Controllers.getDecorator().contentWidthProperty().multiply(0.7)); ImageContainer imageContainer = new ImageContainer(40); - imageContainer.setImage(getOrCreateIcon(shaderFile)); + imageContainer.setImage(getOrCreateIcon(shaderPackFile)); TwoLineListItem title = new TwoLineListItem(); - title.setTitle(shaderFile.getFileName()); - title.setSubtitle(shaderFile.getFile().getFileName().toString()); - title.addTag(switch (shaderFile.getLoaderType()) { + title.setTitle(shaderPackFile.getFileName()); + title.setSubtitle(shaderPackFile.getFile().getFileName().toString()); + title.addTag(switch (shaderPackFile.getLoaderType()) { case OPTIFINE_IRIS -> i18n("shaderpack.loader.optifine_iris"); case APERTURE -> i18n("shaderpack.loader.aperture"); }); @@ -485,8 +471,8 @@ public ShaderInfoDialog(ShaderFile shaderFile) { setHeading(titleContainer); Label description = new Label(); - if (shaderFile.getMeta() != null && shaderFile.getMeta().description() != null) - description.setText(shaderFile.getMeta().description()); + if (shaderPackFile.getMeta() != null && shaderPackFile.getMeta().description() != null) + description.setText(shaderPackFile.getMeta().description()); description.setWrapText(true); FXUtils.copyOnDoubleClick(description); @@ -510,7 +496,7 @@ public ShaderInfoDialog(ShaderFile shaderFile) { RemoteAddonRepository repository = item.getValue(); JFXHyperlink button = new JFXHyperlink(i18n(item.getKey())); Task.runAsync(() -> { - Optional versionOptional = repository.getRemoteVersionByLocalFile(shaderFile.getFile()); + Optional versionOptional = repository.getRemoteVersionByLocalFile(shaderPackFile.getFile()); if (versionOptional.isPresent()) { RemoteAddon remoteAddon = repository.getAddonById(DownloadProviders.getDownloadProvider(), versionOptional.get().projectId()); FXUtils.runInFX(() -> { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFile.java similarity index 73% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFile.java index b0a2a0ddaa0..daad64fe643 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFile.java @@ -33,9 +33,9 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public sealed abstract class ShaderFile extends LocalAddonFile implements Comparable permits ShaderZipFile, ShaderFolder { - public static @Nullable ShaderFile fromFile(Path file) throws IOException { - return Files.isRegularFile(file) ? ShaderZipFile.load(file) : ShaderFolder.load(file); +public sealed abstract class ShaderPackFile extends LocalAddonFile implements Comparable permits ShaderPackZipFile, ShaderPackFolder { + public static @Nullable ShaderPackFile fromFile(Path file) throws IOException { + return Files.isRegularFile(file) ? ShaderPackZipFile.load(file) : ShaderPackFolder.load(file); } public static boolean isFileShaderPack(Path file) { @@ -57,14 +57,12 @@ public static boolean isFileShaderPack(Path file) { protected final String fileNameWithoutExtension; protected final ShaderLoaderType loaderType; protected final @Nullable ShaderPackMeta shaderPackMeta; - protected final @Nullable Image icon; - protected ShaderFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta, @Nullable Image icon) { + protected ShaderPackFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta) { this.file = file; this.fileNameWithoutExtension = FileUtils.getNameWithoutExtension(file); this.loaderType = loaderType; this.shaderPackMeta = shaderPackMeta; - this.icon = icon; } @Override @@ -85,9 +83,7 @@ public ShaderLoaderType getLoaderType() { return shaderPackMeta; } - public @Nullable Image getIcon() { - return icon; - } + public abstract @Nullable Image loadIcon(); public @NotNull String getName() { return Optional.ofNullable(getMeta()).map(ShaderPackMeta::name).orElse(getFile().getFileName().toString()); @@ -115,7 +111,23 @@ public void setOld(boolean old) throws IOException { } @Override - public int compareTo(@NotNull ShaderFile other) { + public int compareTo(@NotNull ShaderPackFile other) { return fileNameWithoutExtension.compareTo(other.fileNameWithoutExtension); } + + @Override + public void onUpdated(String newFileNameWithExt) { + super.onUpdated(newFileNameWithExt); + var configPath = getFile().resolveSibling(getFile().getFileName() + ".txt"); + var newConfigPath = getFile().resolveSibling(newFileNameWithExt + ".txt"); + if (Files.isRegularFile(configPath)) { + try { + Files.move(configPath, newConfigPath); + } catch (IOException e) { + LOG.warning("Failed to rename shader config file " + configPath, e); + } + } else { + LOG.warning("Failed to rename shader config file " + configPath + " because the file doesn't exist"); + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java similarity index 61% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java index d2a6ff62d6d..a4d912afc4a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java @@ -28,9 +28,9 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; -final class ShaderFolder extends ShaderFile { +final class ShaderPackFolder extends ShaderPackFile { - public static @Nullable ShaderFolder load(Path file) { + public static @Nullable ShaderPackFolder load(Path file) { Path shadersPath = file.resolve("shaders"); if (!Files.isDirectory(shadersPath)) return null; @@ -40,27 +40,24 @@ final class ShaderFolder extends ShaderFile { } catch (IOException e) { LOG.warning("Failed to load shader metadata", e); } - byte[] iconData = null; - Image icon = null; - try { - iconData = Files.readAllBytes(shadersPath.resolve("pack.png")); - } catch (IOException e) { - LOG.warning("Failed to read aperture shader icon", e); - } - if (iconData != null) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { - icon = new Image(inputStream, 64, 64, true, true); - } catch (Exception e) { - LOG.warning("Failed to load aperture shader icon", e); - } - } + if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture - return new ShaderFolder(file, ShaderLoaderType.APERTURE, meta, icon); + return new ShaderPackFolder(file, ShaderLoaderType.APERTURE, meta); } - return new ShaderFolder(file, ShaderLoaderType.OPTIFINE_IRIS, meta, icon); + return new ShaderPackFolder(file, ShaderLoaderType.OPTIFINE_IRIS, meta); + } + + private ShaderPackFolder(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta) { + super(file, loaderType, shaderPackMeta); } - private ShaderFolder(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta, @Nullable Image icon) { - super(file, loaderType, shaderPackMeta, icon); + public @Nullable Image loadIcon() { + Path iconPath = getFile().resolve("shaders").resolve("pack.png"); + try (var inputStream = new ByteArrayInputStream(Files.readAllBytes(iconPath))) { + return new Image(inputStream, 64, 64, true, true); + } catch (Exception e) { + LOG.warning("Failed to load shader pack icon in " + iconPath, e); + } + return null; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackManager.java new file mode 100644 index 00000000000..75ec7732a62 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackManager.java @@ -0,0 +1,111 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.addon.shader; + +import org.jackhuang.hmcl.addon.LocalAddonManager; +import org.jackhuang.hmcl.game.DefaultGameInstance; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.Unmodifiable; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public class ShaderPackManager extends LocalAddonManager { + + public ShaderPackManager(DefaultGameInstance instance) { + super(instance); + } + + private boolean loaded = false; + + @Override + public Path getDirectory() { + return instance.getShadersDirectory(); + } + + @Override + public @Unmodifiable List getLocalFiles() throws IOException { + lock.lock(); + try { + if (!loaded) + refresh(); + return super.getLocalFiles(); + } finally { + lock.unlock(); + } + } + + @Override + public Comparator getComparator() { + return ShaderPackFile::compareTo; + } + + private void addShaderPackInfo(Path file) throws IOException { + ShaderPackFile shaderPackFile = ShaderPackFile.fromFile(file); + if (shaderPackFile != null) localFiles.add(shaderPackFile); + } + + @Override + public void refresh() throws IOException { + lock.lock(); + try { + localFiles.clear(); + + if (Files.isDirectory(getDirectory())) { + try (DirectoryStream directoryStream = Files.newDirectoryStream(getDirectory())) { + for (Path item : directoryStream) { + try { + addShaderPackInfo(item); + } catch (IOException e) { + LOG.warning("Failed to load resource pack " + item, e); + } + } + } + } + + loaded = true; + } finally { + lock.unlock(); + } + } + + public void importShaderPack(Path file) throws IOException, IllegalArgumentException { + if (ShaderPackFile.isFileShaderPack(file)) { + Files.createDirectories(getDirectory()); + FileUtils.copyTo(file, getDirectory()); + + loaded = false; + } else { + throw new IllegalArgumentException("File is not shader pack"); + } + } + + public void removeShaderPacks(List files) throws IOException { + for (var file : files) { + file.delete(); + } + + loaded = false; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java similarity index 66% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java index d713badd1a8..929ec82f36e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java @@ -37,9 +37,9 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; -final class ShaderZipFile extends ShaderFile { +final class ShaderPackZipFile extends ShaderPackFile { - public static @Nullable ShaderZipFile load(Path file) throws IOException { + public static @Nullable ShaderPackZipFile load(Path file) throws IOException { if (!Files.isRegularFile(file) || !file.toString().toLowerCase(Locale.ROOT).endsWith(".zip")) return null; try (var zipSystem = CompressingUtils.createReadOnlyZipFileSystem(file)) { @@ -57,30 +57,35 @@ final class ShaderZipFile extends ShaderFile { } catch (IOException e) { LOG.warning("Failed to load aperture shader metadata", e); } - byte[] iconData = null; - Image icon = null; - try { - iconData = Files.readAllBytes(shadersPath.resolve("pack.png")); - } catch (IOException e) { - LOG.warning("Failed to read aperture shader icon", e); - } - if (iconData != null) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { - icon = new Image(inputStream, 64, 64, true, true); - } catch (Exception e) { - LOG.warning("Failed to load aperture shader icon", e); - } - } + + String shadersEntry = shadersPath.toAbsolutePath().normalize().toString().substring(1) + '/'; if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture - return new ShaderZipFile(file, ShaderLoaderType.APERTURE, meta, icon); + return new ShaderPackZipFile(file, shadersEntry, ShaderLoaderType.APERTURE, meta); } - return new ShaderZipFile(file, ShaderLoaderType.OPTIFINE_IRIS, meta, icon); + return new ShaderPackZipFile(file, shadersEntry, ShaderLoaderType.OPTIFINE_IRIS, meta); } } } - private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta, @Nullable Image icon) { - super(file, loaderType, shaderPackMeta, icon); + private final String shadersEntry; + + private ShaderPackZipFile(Path file, String shadersEntry, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta) { + super(file, loaderType, shaderPackMeta); + this.shadersEntry = shadersEntry; + } + + @Override + public @Nullable Image loadIcon() { + try (var tree = CompressingUtils.openZipTree(getFile())) { + var entry = tree.getEntry(shadersEntry + "pack.png"); + if (entry == null) return null; + try (var inputStream = new ByteArrayInputStream(tree.readBinaryEntry(entry))) { + return new Image(inputStream, 64, 64, true, true); + } + } catch (Exception e) { + LOG.warning("Failed to load shader pack icon in file %s!/%s".formatted(getFile(), shadersEntry), e); + } + return null; } @Override @@ -97,20 +102,4 @@ private ShaderZipFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPa if (remoteVersions.isEmpty()) return null; return new AddonUpdate(source, RemoteAddon.Type.SHADER_PACK, this, currentVersion.get(), remoteVersions.get(0)); } - - @Override - public void onUpdated(String newFileNameWithExt) { - super.onUpdated(newFileNameWithExt); - var configPath = getFile().resolveSibling(getFileName() + ".zip.txt"); - var newConfigPath = getFile().resolveSibling(newFileNameWithExt + ".txt"); - if (Files.isRegularFile(configPath)) { - try { - Files.move(configPath, newConfigPath); - } catch (IOException e) { - LOG.warning("Failed to rename shader config file " + configPath, e); - } - } else { - LOG.warning("Failed to rename shader config file " + configPath + " because the file doesn't exist"); - } - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java index 58df20f457b..eb21027bc77 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java @@ -20,6 +20,7 @@ import com.google.gson.JsonParseException; import org.jackhuang.hmcl.addon.mod.ModManager; import org.jackhuang.hmcl.addon.pack.resourcepack.ResourcePackManager; +import org.jackhuang.hmcl.addon.shader.ShaderPackManager; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -73,6 +74,9 @@ public abstract class DefaultGameInstance implements GameInstance { /// Lazily created resource-pack manager for this snapshot member only. private @Nullable ResourcePackManager resourcePackManager; + /// Lazily created shader-pack manager for this snapshot member only. + private @Nullable ShaderPackManager shaderPackManager; + protected DefaultGameInstance( DefaultGameRepositorySnapshot snapshot, GameInstanceID id, @@ -214,6 +218,20 @@ public ResourcePackManager getResourcePackManager() { return resourcePackManager; } + /// Returns the shader-pack manager for this snapshot member. + /// + /// The manager is created on first use and is not shared with other snapshot wrappers. After a + /// repository refresh or COW publish, callers should obtain the manager from the current + /// instance again. + /// + /// @return the shader-pack manager + public ShaderPackManager getShaderPackManager() { + if (shaderPackManager == null) { + shaderPackManager = new ShaderPackManager(this); + } + return shaderPackManager; + } + /// Detects the Minecraft game version from this instance's primary client jar. /// /// @return the detected version, or [GameVersionNumber#unknown()] when detection fails From 10c6fe41f32df114d130ab211992670247748443 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 29 Aug 2026 21:33:53 +0800 Subject: [PATCH 28/36] update --- .../org/jackhuang/hmcl/ui/instances/GameInstancePage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameInstancePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameInstancePage.java index 4cea78de6b9..9807234009b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameInstancePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameInstancePage.java @@ -60,8 +60,8 @@ public class GameInstancePage extends DecoratorAnimatedPage implements Decorator private final TabHeader.Tab worldListTab = new TabHeader.Tab<>("worldList"); private final TabHeader.Tab schematicsTab = new TabHeader.Tab<>("schematicsTab"); private final TabHeader.Tab resourcePackTab = new TabHeader.Tab<>("resourcePackTab"); - private final TabHeader.Tab shaderListTab = new TabHeader.Tab<>("shaderListTab"); - private final TransitionPane transitionPane = new TransitionPane(); + private final TabHeader.Tab shaderListTab = new TabHeader.Tab<>("shaderListTab"); + private final TransitionPane transitionPane = new TransitionPane(); private final BooleanProperty currentInstanceUpgradable = new SimpleBooleanProperty(); private final ObjectProperty instance = new SimpleObjectProperty<>(this, "instance"); From 7cf44cba3f6f06c9dcebbacaa5ca882758890002 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 9 Sep 2026 19:04:11 +0800 Subject: [PATCH 29/36] update --- .../hmcl/ui/instances/ModListPage.java | 20 +++++++------- .../ui/instances/ResourcePackListPage.java | 1 + .../hmcl/ui/instances/ShaderPackListPage.java | 9 +++++++ .../org/jackhuang/hmcl/util/i18n/I18n.java | 7 +++++ .../org/jackhuang/hmcl/addon/AddonLoader.java | 12 ++------- .../jackhuang/hmcl/addon/AddonLoaderType.java | 20 ++++++++++++++ .../org/jackhuang/hmcl/addon/LoaderType.java | 27 ------------------- .../hmcl/addon/shader/ShaderLoaderType.java | 22 ++++++++++----- 8 files changed, 65 insertions(+), 53 deletions(-) delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java index 81468191662..4d5e93d3b78 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java @@ -364,7 +364,7 @@ private static final class ModListPageSkin extends SkinBase { private final HBox toolbarNormal; private final HBox toolbarSelecting; - private final JFXListView listView; + private final JFXListView listView; /// Whether the search mechanism is currently active. private final BooleanProperty isSearching = new SimpleBooleanProperty(false); @@ -431,7 +431,7 @@ public ModListPageSkin(ModListPage skinnable) { createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () -> skinnable.checkUpdates( listView.getItems().stream() - .map(ModListPage.ModInfoObject::getModInfo) + .map(ModInfoObject::getModInfo) .toList() ) ), @@ -465,7 +465,7 @@ public ModListPageSkin(ModListPage skinnable) { createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () -> skinnable.checkUpdates( listView.getSelectionModel().getSelectedItems().stream() - .map(ModListPage.ModInfoObject::getModInfo) + .map(ModInfoObject::getModInfo) .toList() ) ), @@ -502,7 +502,7 @@ public ModListPageSkin(ModListPage skinnable) { ComponentList.setVgrow(center, Priority.ALWAYS); center.loadingProperty().bind(skinnable.loadingProperty()); - listView.setCellFactory(x -> new ModListPage.ModInfoListCell(listView, skinnable)); + listView.setCellFactory(x -> new ModInfoListCell(listView, skinnable)); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); StackPane placeholderContainer = new StackPane(); @@ -522,17 +522,17 @@ public ModListPageSkin(ModListPage skinnable) { listView.setPlaceholder(placeholderContainer); Bindings.bindContent(listView.getItems(), skinnable.getItems()); - skinnable.getItems().addListener((ListChangeListener) c -> { + skinnable.getItems().addListener((ListChangeListener) c -> { if (isSearching.get()) { search(); } }); listView.setOnContextMenuRequested(event -> { - ModListPage.ModInfoObject selectedItem = listView.getSelectionModel().getSelectedItem(); + ModInfoObject selectedItem = listView.getSelectionModel().getSelectedItem(); if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) { listView.getSelectionModel().clearSelection(); - Controllers.dialog(new ModListPage.ModInfoDialog(selectedItem)); + Controllers.dialog(new ModInfoDialog(selectedItem)); } }); @@ -577,7 +577,7 @@ private void search() { } // Do we need to search in the background thread? - for (ModListPage.ModInfoObject item : getSkinnable().getItems()) { + for (ModInfoObject item : getSkinnable().getItems()) { LocalModFile modInfo = item.getModInfo(); if (predicate.test(modInfo.getFileName()) || predicate.test(modInfo.getName()) @@ -760,7 +760,7 @@ private static final class ModInfoDialog extends JFXDialogLayout { getActions().add(officialPageButton); } - if (modInfo.getModTranslations() == null || StringUtils.isBlank(modInfo.getModTranslations().getMcmod())) { + if (modInfo.getModTranslations() == null || StringUtils.isBlank(modInfo.getModTranslations().mcmod())) { JFXHyperlink searchButton = new JFXHyperlink(i18n("mods.mcmod.search")); searchButton.setExternalLink(NetworkUtils.withQuery("https://search.mcmod.cn/s", mapOf( pair("key", modInfo.getModInfo().getName()), @@ -849,7 +849,7 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { String displayName = modInfo.getName(); if (modTranslations != null && I18n.isUseChinese()) { - String chineseName = modTranslations.getName(); + String chineseName = modTranslations.name(); if (StringUtils.containsChinese(chineseName)) { if (StringUtils.containsEmoji(chineseName)) { StringBuilder builder = new StringBuilder(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java index ce1ffa639bd..a836d5c431e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java @@ -627,6 +627,7 @@ private static final class ResourcePackInfoDialog extends JFXDialogLayout { imageContainer.setImage(packInfoObject.getIcon()); TwoLineListItem title = new TwoLineListItem(); + title.getTitleLabel().setWrapText(true); title.setTitle(pack.getFileName()); title.setSubtitle(pack.getFileNameWithExtension()); var compatibility = pack.getCompatibility(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java index bd7062a3d51..afd34fd5880 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java @@ -37,6 +37,7 @@ import javafx.scene.layout.StackPane; import javafx.stage.FileChooser; import javafx.util.Duration; +import org.jackhuang.hmcl.addon.AddonLoader; import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository; @@ -460,6 +461,7 @@ public ShaderPackInfoDialog(ShaderPackFile shaderPackFile) { imageContainer.setImage(getOrCreateIcon(shaderPackFile)); TwoLineListItem title = new TwoLineListItem(); + title.getTitleLabel().setWrapText(true); title.setTitle(shaderPackFile.getFileName()); title.setSubtitle(shaderPackFile.getFile().getFileName().toString()); title.addTag(switch (shaderPackFile.getLoaderType()) { @@ -500,6 +502,13 @@ public ShaderPackInfoDialog(ShaderPackFile shaderPackFile) { if (versionOptional.isPresent()) { RemoteAddon remoteAddon = repository.getAddonById(DownloadProviders.getDownloadProvider(), versionOptional.get().projectId()); FXUtils.runInFX(() -> { + Set tags = new LinkedHashSet<>(); + for (AddonLoader loader : versionOptional.get().loaders()) { + String tag = I18n.translateLoaderName(loader); + if (tag != null) tags.add(tag); + } + title.addTagsIfNotExist(tags); + button.setExternalLink(remoteAddon.pageUrl()); button.setDisable(false); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java index bcb3c192085..8bb84614bb8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java @@ -20,6 +20,7 @@ import org.jackhuang.hmcl.addon.AddonLoader; import org.jackhuang.hmcl.addon.AddonLoaderType; import org.jackhuang.hmcl.addon.mod.ModLoaderType; +import org.jackhuang.hmcl.addon.shader.ShaderLoaderType; import org.jackhuang.hmcl.download.ComponentRemoteVersion; import org.jackhuang.hmcl.download.game.GameRemoteVersion; import org.jackhuang.hmcl.util.StringUtils; @@ -140,6 +141,12 @@ public static String translateLoaderType(AddonLoaderType loaderType) { case LEGACY_FABRIC -> i18n("install.installer.legacyfabric"); default -> modLoaderType.displayName(); }; + } else if (loaderType instanceof ShaderLoaderType shaderLoaderType) { + return switch (shaderLoaderType) { + case OPTIFINE_IRIS -> i18n("shaderpack.loader.optifine_iris"); + case APERTURE -> i18n("shaderpack.loader.aperture"); + default -> shaderLoaderType.displayName(); + }; } return loaderType.displayName(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonLoader.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonLoader.java index 6da37553d56..b4925bada6d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonLoader.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonLoader.java @@ -17,13 +17,13 @@ */ package org.jackhuang.hmcl.addon; -import org.jackhuang.hmcl.addon.mod.ModLoaderType; import org.jackhuang.hmcl.util.StringUtils; import org.jetbrains.annotations.NotNullByDefault; import org.jetbrains.annotations.Nullable; @NotNullByDefault public record AddonLoader(String name, @Nullable AddonLoaderType type) { + public static boolean mightBeLoader(String str) { if (StringUtils.isBlank(str) || !StringUtils.isASCII(str) @@ -38,15 +38,7 @@ public static boolean mightBeLoader(String str) { } public static AddonLoader of(String name) { - for (var type : ModLoaderType.values()) { - for (String loaderName : type.names()) { - if (name.equalsIgnoreCase(loaderName)) { - return new AddonLoader(name, type); - } - } - - } - return new AddonLoader(name, null); + return new AddonLoader(name, AddonLoaderType.of(name)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonLoaderType.java index c144b20630a..db003a8ccbd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonLoaderType.java @@ -17,15 +17,35 @@ */ package org.jackhuang.hmcl.addon; +import org.jackhuang.hmcl.addon.mod.ModLoaderType; +import org.jackhuang.hmcl.addon.shader.ShaderLoaderType; import org.jetbrains.annotations.NotNullByDefault; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.Set; +import java.util.stream.Stream; /// For mods and shaders @NotNullByDefault public interface AddonLoaderType { + List VALUES = Stream.concat( + Arrays.stream(ModLoaderType.values()), + Arrays.stream(ShaderLoaderType.values()) + ).toList(); + + static @Nullable AddonLoaderType of(String name) { + for (var type : VALUES) { + if (type.names().contains(name.toLowerCase(Locale.ROOT))) + return type; + } + return null; + } + String displayName(); @Unmodifiable Set names(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java deleted file mode 100644 index eaf5fa6bc34..00000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LoaderType.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2026 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.addon; - -import java.util.Set; - -/// For mods and shaders -public interface LoaderType { - - Set names(); - -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java index a42c757db35..191add311d1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderLoaderType.java @@ -17,22 +17,32 @@ */ package org.jackhuang.hmcl.addon.shader; -import org.jackhuang.hmcl.addon.LoaderType; +import org.jackhuang.hmcl.addon.AddonLoaderType; +import org.jetbrains.annotations.NotNullByDefault; +import org.jetbrains.annotations.Unmodifiable; import java.util.Set; -public enum ShaderLoaderType implements LoaderType { - OPTIFINE_IRIS("optifine", "iris"), - APERTURE("aperture"); +@NotNullByDefault +public enum ShaderLoaderType implements AddonLoaderType { + OPTIFINE_IRIS("Optifine/Iris", "optifine", "iris"), + APERTURE("Aperture", "aperture"); + private final String displayName; private final Set names; - ShaderLoaderType(String... names) { + ShaderLoaderType(String displayName, String... names) { + this.displayName = displayName; this.names = Set.of(names); } @Override - public Set names() { + public String displayName() { + return displayName; + } + + @Override + public @Unmodifiable Set names() { return names; } } From dbbd6d547447e611ad3f77e5002356b3ad792509 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 10 Sep 2026 15:47:30 +0800 Subject: [PATCH 30/36] prepare for merging --- .../ui/instances/ResourcePackListPage.java | 2 +- .../pack/resourcepack/ResourcePackFile.java | 27 +++++------- .../pack/resourcepack/ResourcePackFolder.java | 29 +++++++------ .../resourcepack/ResourcePackManager.java | 8 ++-- .../resourcepack/ResourcePackZipFile.java | 42 +++++++++---------- 5 files changed, 53 insertions(+), 55 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java index a836d5c431e..0353553bd7b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java @@ -516,7 +516,7 @@ public BooleanProperty enabledProperty() { } Image getIcon() { - Image image = file.getIcon(); + Image image = file.loadIcon(); if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 || (Math.abs(image.getWidth() - image.getHeight()) >= 1)) { image = UNKNOWN_PACK_IMAGE.get(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java index e9c15697aa5..30a2f6b13bd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java @@ -31,12 +31,11 @@ import java.nio.file.Path; import java.util.List; import java.util.Locale; -import java.util.Objects; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public sealed abstract class ResourcePackFile extends LocalAddonFile implements Comparable permits ResourcePackFolder, ResourcePackZipFile { - static @Nullable ResourcePackFile fromFile(ResourcePackManager manager, Path path) throws IOException { + static ResourcePackFile fromFile(ResourcePackManager manager, Path path) { return Files.isRegularFile(path) ? ResourcePackZipFile.load(manager, path) : ResourcePackFolder.load(manager, path); } @@ -54,20 +53,18 @@ public static boolean isFileResourcePack(Path file) { protected final ResourcePackManager manager; protected Path file; - protected final PackMcMeta meta; - protected final Image icon; + protected final PackMcMeta.PackInfo packInfo; protected final String fileName; protected final String fileNameWithExtension; private Compatibility compatibility = null; - protected ResourcePackFile(ResourcePackManager manager, Path file, @NotNull PackMcMeta meta, @Nullable Image icon) { + protected ResourcePackFile(ResourcePackManager manager, Path file, @Nullable PackMcMeta.PackInfo packInfo) { super(); this.manager = manager; this.file = file; - this.meta = Objects.requireNonNull(meta); - this.icon = icon; + this.packInfo = packInfo; this.fileName = StringUtils.parseColorEscapes(FileUtils.getNameWithoutExtension(file)); this.fileNameWithExtension = file.getFileName().toString(); @@ -124,21 +121,19 @@ public final boolean keepOldFiles() { public void markDisabled() { } - public @NotNull PackMcMeta getMeta() { - return meta; - } - - // 64 * 64 - public @Nullable Image getIcon() { - return icon; + public @Nullable PackMcMeta.PackInfo getPackInfo() { + return packInfo; } @Nullable public LocalAddonFile.Description getDescription() { - if (getMeta().pack() == null) return null; - return getMeta().pack().description(); + if (getPackInfo() == null) return null; + return getPackInfo().description(); } + // 64*64 + public abstract @Nullable Image loadIcon(); + @Override public int compareTo(@NotNull ResourcePackFile other) { return this.fileNameWithExtension.compareTo(other.fileNameWithExtension); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java index 607f2775c41..e2cdedbfbe8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFolder.java @@ -22,7 +22,6 @@ import org.jetbrains.annotations.Nullable; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -30,33 +29,37 @@ final class ResourcePackFolder extends ResourcePackFile { - public static @Nullable ResourcePackFolder load(ResourcePackManager manager, Path path) { + public static ResourcePackFolder load(ResourcePackManager manager, Path path) { PackMcMeta meta = null; try { meta = PackMcMeta.fromNonNullJsonFile(path.resolve("pack.mcmeta")); } catch (Exception e) { LOG.warning("Failed to parse resource pack meta", e); } - if (meta == null) return null; + return new ResourcePackFolder(manager, path, meta != null ? meta.pack() : null); + } + + private ResourcePackFolder(ResourcePackManager manager, Path path, PackMcMeta.PackInfo info) { + super(manager, path, info); + } + + @Override + public @Nullable Image loadIcon() { byte[] iconData = null; - Image icon = null; try { - iconData = Files.readAllBytes(path.resolve("pack.png")); - } catch (IOException e) { - LOG.warning("Failed to read resource pack icon", e); + iconData = Files.readAllBytes(getFile().resolve("pack.png")); + } catch (Exception e) { + LOG.warning("Failed to load resource pack icon", e); } + if (iconData != null) { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { - icon = new Image(inputStream, 64, 64, true, true); + return new Image(inputStream, 64, 64, true, true); } catch (Exception e) { LOG.warning("Failed to load resource pack icon", e); } } - return new ResourcePackFolder(manager, path, meta, icon); - } - - private ResourcePackFolder(ResourcePackManager manager, Path path, PackMcMeta meta, @Nullable Image icon) { - super(manager, path, meta, icon); + return null; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java index 68c6b14b4f9..9af655db3e0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java @@ -549,18 +549,18 @@ public boolean rename(String oldName, String newName) { } public ResourcePackFile.Compatibility getCompatibility(@NotNull ResourcePackFile resourcePack) { - if (resourcePack.getMeta().pack() == null) + if (resourcePack.getPackInfo() == null) return ResourcePackFile.Compatibility.MISSING_PACK_META; if (this.getRequiredVersion().isUnspecified()) return ResourcePackFile.Compatibility.MISSING_GAME_META; int requiredMajor = getRequiredVersion().majorVersion(); VersionRange versionRange; if (requiredMajor > 64) - versionRange = getResourcePackVersionRangeNew(resourcePack.getMeta().pack()); + versionRange = getResourcePackVersionRangeNew(resourcePack.getPackInfo()); else if (requiredMajor > 15) - versionRange = getResourcePackVersionRangeOld(resourcePack.getMeta().pack()); + versionRange = getResourcePackVersionRangeOld(resourcePack.getPackInfo()); else - versionRange = getResourcePackVersionRangeOldest(resourcePack.getMeta().pack()); + versionRange = getResourcePackVersionRangeOldest(resourcePack.getPackInfo()); if (versionRange.isEmpty()) return ResourcePackFile.Compatibility.INVALID; if (versionRange.getMaximum().compareTo(this.getRequiredVersion()) < 0) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java index cbf4adc19c4..28efc67c31a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackZipFile.java @@ -36,41 +36,41 @@ final class ResourcePackZipFile extends ResourcePackFile { - public static @Nullable ResourcePackZipFile load(ResourcePackManager manager, Path path) throws IOException { + public static ResourcePackZipFile load(ResourcePackManager manager, Path path) { PackMcMeta meta = null; - byte[] iconData = null; - try (var zipFileTree = CompressingUtils.openZipTree(path)) { - try { - meta = PackMcMeta.fromNonNullJson(zipFileTree.readTextEntry("/pack.mcmeta")); - } catch (Exception e) { - LOG.warning("Failed to parse resource pack meta", e); - } - if (meta == null) return null; + meta = PackMcMeta.fromNonNullJson(zipFileTree.readTextEntry("/pack.mcmeta")); + } catch (Exception e) { + LOG.warning("Failed to parse resource pack meta", e); + } + + return new ResourcePackZipFile(manager, path, meta != null ? meta.pack() : null); + } + private ResourcePackZipFile(ResourcePackManager manager, Path path, PackMcMeta.PackInfo info) { + super(manager, path, info); + } + + @Override + public @Nullable Image loadIcon() { + byte[] iconData = null; + try (var zipFileTree = CompressingUtils.openZipTree(getFile())) { var iconEntry = zipFileTree.getEntry("/pack.png"); if (iconEntry != null) { - try { - iconData = zipFileTree.readBinaryEntry(iconEntry); - } catch (Exception e) { - LOG.warning("Failed to load resource pack icon", e); - } + iconData = zipFileTree.readBinaryEntry(iconEntry); } + } catch (Exception e) { + LOG.warning("Failed to load resource pack icon", e); } - Image icon = null; if (iconData != null) { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) { - icon = new Image(inputStream, 64, 64, true, true); + return new Image(inputStream, 64, 64, true, true); } catch (Exception e) { LOG.warning("Failed to load resource pack icon", e); } } - return new ResourcePackZipFile(manager, path, meta, icon); - } - - private ResourcePackZipFile(ResourcePackManager manager, Path path, PackMcMeta meta, @Nullable Image icon) { - super(manager, path, meta, icon); + return null; } @Override From cb7a26733e0b5443a5b0c3e911ec90559935eccb Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 10 Sep 2026 16:21:32 +0800 Subject: [PATCH 31/36] complete merging --- .../ui/instances/ResourcePackListPage.java | 4 +- .../hmcl/ui/instances/ShaderPackListPage.java | 88 ++++++++++++------- .../pack/resourcepack/ResourcePackFile.java | 1 + 3 files changed, 59 insertions(+), 34 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java index 6f13f221502..41b216cb0ce 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java @@ -522,9 +522,8 @@ private Image getDefaultIcon() { private Image loadIcon() { Image icon = file.loadIcon(); - if (icon != null && !icon.isError() && icon.getWidth() > 0 && icon.getHeight() > 0 && Math.abs(icon.getWidth() - icon.getHeight()) < 1) { + if (icon != null && !icon.isError() && icon.getWidth() > 0 && icon.getHeight() > 0 && Math.abs(icon.getWidth() - icon.getHeight()) < 1) return icon; - } return getDefaultIcon(); } } @@ -588,6 +587,7 @@ protected void updateControl(ResourcePackInfoObject item, boolean empty) { if (empty || item == null) return; ResourcePackFile file = item.getFile(); + item.iconCache.attachValue(imageContainer.imageProperty(), new WeakReference<>(itemProperty())); content.getTags().clear(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java index afd34fd5880..2db2e8933b3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java @@ -57,10 +57,12 @@ import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.javafx.ItemPropertyAsyncCache; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.lang.ref.WeakReference; import java.nio.file.Path; import java.util.*; import java.util.concurrent.locks.ReentrantLock; @@ -73,7 +75,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class ShaderPackListPage extends ListPageBase { +public class ShaderPackListPage extends ListPageBase { private final ReentrantLock lock = new ReentrantLock(); @@ -121,7 +123,7 @@ public void refresh() { lock.lock(); try { shaderPackManager.refresh(); - return shaderPackManager.getLocalFiles(); + return shaderPackManager.getLocalFiles().stream().map(ShaderPackInfoObject::new).toList(); } finally { lock.unlock(); } @@ -161,9 +163,9 @@ public void addFiles(List files) { }).start(); } - public void removeFiles(List selectedItems) { + private void removeSelected(List selectedItems) { try { - shaderPackManager.removeShaderPacks(selectedItems); + shaderPackManager.removeShaderPacks(selectedItems.stream().map(ShaderPackInfoObject::getFile).toList()); } catch (IOException e) { Controllers.dialog(i18n("shaderpack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR); LOG.warning("Failed to delete shader packs", e); @@ -216,20 +218,38 @@ private void onOpenFolder() { } } - private static Image getOrCreateIcon(ShaderPackFile shaderPackFile) { - Image image = shaderPackFile.loadIcon(); - if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 || - (Math.abs(image.getWidth() - image.getHeight()) >= 1)) { - image = switch (shaderPackFile.getLoaderType()) { + public static final class ShaderPackInfoObject { + private final ShaderPackFile file; + + private final ItemPropertyAsyncCache iconCache; + + public ShaderPackInfoObject(ShaderPackFile file) { + this.file = file; + + this.iconCache = new ItemPropertyAsyncCache.Soft<>(this, this::loadIcon, this::getDefaultIcon); + } + + public ShaderPackFile getFile() { + return file; + } + + private Image getDefaultIcon() { + return switch (file.getLoaderType()) { case OPTIFINE_IRIS -> FXUtils.newBuiltinImage("/assets/img/opti-iris.png"); - default -> ResourcePackListPage.UNKNOWN_PACK_IMAGE.get(); + default -> FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"); }; } - return image; + + private Image loadIcon() { + Image icon = file.loadIcon(); + if (icon != null && !icon.isError() && icon.getWidth() > 0 && icon.getHeight() > 0 && Math.abs(icon.getWidth() - icon.getHeight()) < 1) + return icon; + return getDefaultIcon(); + } } private static final class ShaderPackListPageSkin extends SkinBase { - private final JFXListView listView; + private final JFXListView listView; private final JFXTextField searchField = new JFXTextField(); private final TransitionPane toolbarPane = new TransitionPane(); @@ -257,11 +277,11 @@ public ShaderPackListPageSkin(ShaderPackListPage control) { toolbarSelecting.getChildren().setAll( createToolbarButton2(i18n("button.remove"), SVG.DELETE_FOREVER, () -> { Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> - control.removeFiles(listView.getSelectionModel().getSelectedItems()), + control.removeSelected(listView.getSelectionModel().getSelectedItems()), null); }), createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () -> - control.checkUpdates(listView.getSelectionModel().getSelectedItems().stream().toList()) + control.checkUpdates(listView.getSelectionModel().getSelectedItems().stream().map(ShaderPackInfoObject::getFile).toList()) ), createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () -> listView.getSelectionModel().selectAll()), @@ -302,7 +322,7 @@ public ShaderPackListPageSkin(ShaderPackListPage control) { createToolbarButton2(i18n("shaderpack.add"), SVG.ADD, control::onAddFiles), createToolbarButton2(i18n("button.reveal_dir"), SVG.FOLDER_OPEN, control::onOpenFolder), createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () -> - control.checkUpdates(listView.getItems().stream().toList()) + control.checkUpdates(listView.getItems().stream().map(ShaderPackInfoObject::getFile).toList()) ), createToolbarButton2(i18n("download"), SVG.DOWNLOAD, control::onDownload), createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)) @@ -339,7 +359,7 @@ public ShaderPackListPageSkin(ShaderPackListPage control) { Bindings.bindContent(listView.getItems(), control.getItems()); listView.setOnContextMenuRequested(event -> { - ShaderPackFile selectedItem = listView.getSelectionModel().getSelectedItem(); + ShaderPackInfoObject selectedItem = listView.getSelectionModel().getSelectedItem(); if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) { listView.getSelectionModel().clearSelection(); Controllers.dialog(new ShaderPackInfoDialog(selectedItem)); @@ -387,10 +407,11 @@ private void search() { } // Do we need to search in the background thread? - for (ShaderPackFile item : getSkinnable().getItems()) { - var meta = item.getMeta(); - if (predicate.test(item.getFile().getFileName().toString()) - || predicate.test(item.getName()) + for (ShaderPackInfoObject item : getSkinnable().getItems()) { + var file = item.getFile(); + var meta = file.getMeta(); + if (predicate.test(file.getFile().getFileName().toString()) + || predicate.test(file.getName()) || (meta != null && (predicate.test(meta.version()) || predicate.test(meta.description())))) { listView.getItems().add(item); } @@ -400,14 +421,14 @@ private void search() { } - private static final class ShaderPackListCell extends MDListCell { + private static final class ShaderPackListCell extends MDListCell { private final ImageContainer imageContainer = new ImageContainer(24); private final TwoLineListItem content = new TwoLineListItem(); private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER); private final JFXButton btnInfo = FXUtils.newToggleButton4(SVG.INFO); - public ShaderPackListCell(JFXListView listView) { + public ShaderPackListCell(JFXListView listView) { super(listView); HBox root = new HBox(8); @@ -426,23 +447,25 @@ public ShaderPackListCell(JFXListView listView) { } @Override - protected void updateControl(ShaderPackFile item, boolean empty) { + protected void updateControl(ShaderPackInfoObject item, boolean empty) { if (empty || item == null) return; - imageContainer.setImage(getOrCreateIcon(item)); + ShaderPackFile file = item.file; + + item.iconCache.attachValue(imageContainer.imageProperty(), new WeakReference<>(itemProperty())); content.getTags().clear(); - content.setTitle(item.getFileName()); - content.setSubtitle(item.getMeta() == null || item.getMeta().name() == null ? "" : item.getMeta().name()); - content.addTag(switch (item.getLoaderType()) { + content.setTitle(file.getFileName()); + content.setSubtitle(file.getMeta() == null || file.getMeta().name() == null ? "" : file.getMeta().name()); + content.addTag(switch (file.getLoaderType()) { case OPTIFINE_IRIS -> i18n("shaderpack.loader.optifine_iris"); case APERTURE -> i18n("shaderpack.loader.aperture"); }); - if (item.getMeta() != null && item.getMeta().version() != null) - content.addTag(item.getMeta().version()); + if (file.getMeta() != null && file.getMeta().version() != null) + content.addTag(file.getMeta().version()); FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager")); - btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(item.getFile())); + btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(file.getFile())); btnInfo.setOnAction(e -> Controllers.dialog(new ShaderPackInfoDialog(item))); } @@ -450,7 +473,8 @@ protected void updateControl(ShaderPackFile item, boolean empty) { private static final class ShaderPackInfoDialog extends JFXDialogLayout { - public ShaderPackInfoDialog(ShaderPackFile shaderPackFile) { + public ShaderPackInfoDialog(ShaderPackInfoObject infoObject) { + ShaderPackFile shaderPackFile = infoObject.file; HBox titleContainer = new HBox(); titleContainer.setSpacing(8); @@ -458,7 +482,7 @@ public ShaderPackInfoDialog(ShaderPackFile shaderPackFile) { maxWidthProperty().bind(Controllers.getDecorator().contentWidthProperty().multiply(0.7)); ImageContainer imageContainer = new ImageContainer(40); - imageContainer.setImage(getOrCreateIcon(shaderPackFile)); + infoObject.iconCache.attachValue(imageContainer.imageProperty(), null); TwoLineListItem title = new TwoLineListItem(); title.getTitleLabel().setWrapText(true); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java index 30a2f6b13bd..773e7f2192e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java @@ -35,6 +35,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; public sealed abstract class ResourcePackFile extends LocalAddonFile implements Comparable permits ResourcePackFolder, ResourcePackZipFile { + static ResourcePackFile fromFile(ResourcePackManager manager, Path path) { return Files.isRegularFile(path) ? ResourcePackZipFile.load(manager, path) : ResourcePackFolder.load(manager, path); } From 17ba204bb592dfe268b92c7a4cb4dae347464201 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 12 Sep 2026 17:48:33 +0800 Subject: [PATCH 32/36] aperture & fix config rename --- .../resourcepack/ResourcePackManager.java | 2 +- .../resourcepack/ResourcePackZipFile.java | 2 +- .../hmcl/addon/shader/ShaderPackFile.java | 56 ++++++++------- .../hmcl/addon/shader/ShaderPackFolder.java | 57 ++++++++++----- .../hmcl/addon/shader/ShaderPackManager.java | 14 +++- .../hmcl/addon/shader/ShaderPackMeta.java | 7 +- .../hmcl/addon/shader/ShaderPackZipFile.java | 71 ++++++++++++------- 7 files changed, 136 insertions(+), 73 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java index 9af655db3e0..3bfa47854c4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackManager.java @@ -517,7 +517,7 @@ public Stream> arePacksEnabled(Stream permits ShaderPackZipFile, ShaderPackFolder { - public static @Nullable ShaderPackFile fromFile(Path file) throws IOException { - return Files.isRegularFile(file) ? ShaderPackZipFile.load(file) : ShaderPackFolder.load(file); + public static @Nullable ShaderPackFile fromFile(ShaderPackManager manager, Path file) throws IOException { + return Files.isRegularFile(file) ? ShaderPackZipFile.load(manager, file) : ShaderPackFolder.load(manager, file); } public static boolean isFileShaderPack(Path file) { @@ -53,16 +55,22 @@ public static boolean isFileShaderPack(Path file) { return false; } + protected final ShaderPackManager manager; protected Path file; - protected final String fileNameWithoutExtension; protected final ShaderLoaderType loaderType; - protected final @Nullable ShaderPackMeta shaderPackMeta; + protected final ShaderPackMeta meta; - protected ShaderPackFile(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta) { + protected final String fileName; + protected final String fileNameWithExtension; + + protected ShaderPackFile(ShaderPackManager manager, Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta meta) { + this.manager = manager; this.file = file; - this.fileNameWithoutExtension = FileUtils.getNameWithoutExtension(file); this.loaderType = loaderType; - this.shaderPackMeta = shaderPackMeta; + this.meta = Objects.requireNonNullElse(meta, ShaderPackMeta.EMPTY); + + this.fileName = FileUtils.getNameWithoutExtension(file); + this.fileNameWithExtension = file.getFileName().toString(); } @Override @@ -72,21 +80,25 @@ public Path getFile() { @Override public String getFileName() { - return fileNameWithoutExtension; + return fileName; + } + + public String getFileNameWithExtension() { + return fileNameWithExtension; } public ShaderLoaderType getLoaderType() { return loaderType; } - public @Nullable ShaderPackMeta getMeta() { - return shaderPackMeta; + public ShaderPackMeta getMeta() { + return meta; } public abstract @Nullable Image loadIcon(); - public @NotNull String getName() { - return Optional.ofNullable(getMeta()).map(ShaderPackMeta::name).orElse(getFile().getFileName().toString()); + public String getName() { + return StringUtils.isBlank(getMeta().name()) ? getFile().getFileName().toString() : getMeta().name(); } @Override @@ -111,23 +123,13 @@ public void setOld(boolean old) throws IOException { } @Override - public int compareTo(@NotNull ShaderPackFile other) { - return fileNameWithoutExtension.compareTo(other.fileNameWithoutExtension); + public int compareTo(ShaderPackFile other) { + return fileName.compareTo(other.fileName); } @Override public void onUpdated(String newFileNameWithExt) { super.onUpdated(newFileNameWithExt); - var configPath = getFile().resolveSibling(getFile().getFileName() + ".txt"); - var newConfigPath = getFile().resolveSibling(newFileNameWithExt + ".txt"); - if (Files.isRegularFile(configPath)) { - try { - Files.move(configPath, newConfigPath); - } catch (IOException e) { - LOG.warning("Failed to rename shader config file " + configPath, e); - } - } else { - LOG.warning("Failed to rename shader config file " + configPath + " because the file doesn't exist"); - } + manager.updateConfigFileName(this, newFileNameWithExt); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java index a4d912afc4a..ec80ba6ab3f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java @@ -18,10 +18,10 @@ package org.jackhuang.hmcl.addon.shader; import javafx.scene.image.Image; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jetbrains.annotations.Nullable; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -30,33 +30,56 @@ final class ShaderPackFolder extends ShaderPackFile { - public static @Nullable ShaderPackFolder load(Path file) { - Path shadersPath = file.resolve("shaders"); - if (!Files.isDirectory(shadersPath)) return null; + public static @Nullable ShaderPackFolder load(ShaderPackManager manager, Path file) { + aperture: + { + Path metaPath = file.resolve("pack.json"); + if (!Files.isRegularFile(metaPath) + || !Files.isDirectory(file.resolve("slang")) + || !Files.isDirectory(file.resolve("src"))) { + break aperture; + } - ShaderPackMeta meta = null; - try { - meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ShaderPackMeta.class); - } catch (IOException e) { - LOG.warning("Failed to load shader metadata", e); + ShaderPackMeta meta = null; + try { + meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, metaPath, ShaderPackMeta.class); + } catch (IOException e) { + LOG.warning("Failed to load shader metadata", e); + } + if (meta == null || StringUtils.isBlank(meta.apertureApiVersion())) break aperture; + + return new ShaderPackFolder(manager, file, ShaderLoaderType.APERTURE, meta); } + { + // Optifine/Iris + Path shadersPath = file.resolve("shaders"); + if (!Files.isDirectory(shadersPath)) return null; + + ShaderPackMeta meta = null; + try { + meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ShaderPackMeta.class); + } catch (IOException e) { + LOG.warning("Failed to load shader metadata", e); + } - if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture - return new ShaderPackFolder(file, ShaderLoaderType.APERTURE, meta); + return new ShaderPackFolder(manager, file, ShaderLoaderType.OPTIFINE_IRIS, meta); } - return new ShaderPackFolder(file, ShaderLoaderType.OPTIFINE_IRIS, meta); } - private ShaderPackFolder(Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta) { - super(file, loaderType, shaderPackMeta); + private ShaderPackFolder(ShaderPackManager manager, Path file, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta) { + super(manager, file, loaderType, shaderPackMeta); } public @Nullable Image loadIcon() { - Path iconPath = getFile().resolve("shaders").resolve("pack.png"); - try (var inputStream = new ByteArrayInputStream(Files.readAllBytes(iconPath))) { + Path iconPath = switch (getLoaderType()) { + case APERTURE -> getFile().resolve("pack.png"); + case OPTIFINE_IRIS -> getFile().resolve("shaders").resolve("pack.png"); + }; + if (!Files.isRegularFile(iconPath)) return null; + try (var inputStream = Files.newInputStream(iconPath)) { return new Image(inputStream, 64, 64, true, true); } catch (Exception e) { - LOG.warning("Failed to load shader pack icon in " + iconPath, e); + LOG.warning("Failed to load shader pack icon at " + iconPath, e); } return null; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackManager.java index 75ec7732a62..bd0c1e96949 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackManager.java @@ -62,7 +62,7 @@ public Comparator getComparator() { } private void addShaderPackInfo(Path file) throws IOException { - ShaderPackFile shaderPackFile = ShaderPackFile.fromFile(file); + ShaderPackFile shaderPackFile = ShaderPackFile.fromFile(this, file); if (shaderPackFile != null) localFiles.add(shaderPackFile); } @@ -108,4 +108,16 @@ public void removeShaderPacks(List files) throws IOException { loaded = false; } + + public void updateConfigFileName(ShaderPackFile pack, String newFileNameWithExt) { + var configPath = pack.getFile().resolveSibling(pack.getFileNameWithExtension() + ".txt"); + if (Files.isRegularFile(configPath)) { + var newConfigPath = pack.getFile().resolveSibling(newFileNameWithExt + ".txt"); + try { + Files.move(configPath, newConfigPath); + } catch (IOException e) { + LOG.warning("Failed to rename shader config file " + configPath, e); + } + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java index d2eed08de1c..6c95b1e2d94 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java @@ -18,10 +18,15 @@ package org.jackhuang.hmcl.addon.shader; import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jetbrains.annotations.Nullable; @JsonSerializable public record ShaderPackMeta(@Nullable String name, @Nullable String version, - @SerializedName(value = "shaderDescription") @Nullable String description) { + @SerializedName(value = "shaderDescription") @Nullable String description, + @SerializedName(value = "api") @Nullable String apertureApiVersion +) { + public static final ShaderPackMeta EMPTY = new ShaderPackMeta(null, null, null, null); + } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java index 929ec82f36e..9f614b44ef5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java @@ -21,11 +21,11 @@ import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jetbrains.annotations.Nullable; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -39,51 +39,72 @@ final class ShaderPackZipFile extends ShaderPackFile { - public static @Nullable ShaderPackZipFile load(Path file) throws IOException { + public static @Nullable ShaderPackZipFile load(ShaderPackManager manager, Path file) throws IOException { if (!Files.isRegularFile(file) || !file.toString().toLowerCase(Locale.ROOT).endsWith(".zip")) return null; - try (var zipSystem = CompressingUtils.createReadOnlyZipFileSystem(file)) { - Path root = zipSystem.getRootDirectories().iterator().next(); - try (Stream stream = Files.walk(root)) { - Path shadersPath = stream - .filter(path -> path.endsWith("shaders")) - .filter(Files::isDirectory) - .findFirst().orElse(null); - if (shadersPath == null) return null; + try (var zipFileSystem = CompressingUtils.createReadOnlyZipFileSystem(file)) { + Path root = zipFileSystem.getRootDirectories().iterator().next(); + aperture: + { + Path metaPath = root.resolve("pack.json"); + if (!Files.isRegularFile(metaPath) + || !Files.isDirectory(root.resolve("src")) + || !Files.isDirectory(root.resolve("slang"))) { + break aperture; + } ShaderPackMeta meta = null; try { - meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ShaderPackMeta.class); + meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, metaPath, ShaderPackMeta.class); } catch (IOException e) { - LOG.warning("Failed to load aperture shader metadata", e); + LOG.warning("Failed to load shader metadata", e); } + if (meta == null || StringUtils.isBlank(meta.apertureApiVersion())) break aperture; - String shadersEntry = shadersPath.toAbsolutePath().normalize().toString().substring(1) + '/'; - if (Files.isRegularFile(shadersPath.resolve("pack.ts"))) { // Aperture - return new ShaderPackZipFile(file, shadersEntry, ShaderLoaderType.APERTURE, meta); + String iconPath = root.resolve("pack.png").toAbsolutePath().normalize().toString(); + return new ShaderPackZipFile(manager, file, iconPath, ShaderLoaderType.APERTURE, meta); + } + + { + // Optifine & Iris + Path shadersPath; + try (Stream stream = Files.walk(root)) { + shadersPath = stream + .filter(path -> path.endsWith("shaders")) + .filter(Files::isDirectory) + .findFirst().orElse(null); + if (shadersPath == null) return null; + } + ShaderPackMeta meta = null; + try { + meta = JsonUtils.fromJsonFile(JsonUtils.LENIENT_GSON, shadersPath.resolve("pack.json"), ShaderPackMeta.class); + } catch (IOException e) { + LOG.warning("Failed to load shader metadata", e); } - return new ShaderPackZipFile(file, shadersEntry, ShaderLoaderType.OPTIFINE_IRIS, meta); + + String iconPath = shadersPath.resolve("pack.png").toAbsolutePath().normalize().toString(); + return new ShaderPackZipFile(manager, file, iconPath, ShaderLoaderType.OPTIFINE_IRIS, meta); } } } - private final String shadersEntry; + private final String iconPath; - private ShaderPackZipFile(Path file, String shadersEntry, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta) { - super(file, loaderType, shaderPackMeta); - this.shadersEntry = shadersEntry; + private ShaderPackZipFile(ShaderPackManager manager, Path file, String iconPath, ShaderLoaderType loaderType, @Nullable ShaderPackMeta shaderPackMeta) { + super(manager, file, loaderType, shaderPackMeta); + this.iconPath = iconPath; } @Override public @Nullable Image loadIcon() { - try (var tree = CompressingUtils.openZipTree(getFile())) { - var entry = tree.getEntry(shadersEntry + "pack.png"); - if (entry == null) return null; - try (var inputStream = new ByteArrayInputStream(tree.readBinaryEntry(entry))) { + try (var zipFileSystem = CompressingUtils.createReadOnlyZipFileSystem(getFile())) { + var path = zipFileSystem.getPath(iconPath); + if (!Files.isRegularFile(path)) return null; + try (var inputStream = Files.newInputStream(zipFileSystem.getPath(iconPath))) { return new Image(inputStream, 64, 64, true, true); } } catch (Exception e) { - LOG.warning("Failed to load shader pack icon in file %s!/%s".formatted(getFile(), shadersEntry), e); + LOG.warning("Failed to load shader pack icon at %s!/%s".formatted(getFile(), iconPath), e); } return null; } From f70dba33acb8e39a4c9909a3de8f51ca846faa1e Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 12 Sep 2026 17:54:22 +0800 Subject: [PATCH 33/36] sync translation --- .../src/main/resources/assets/lang/I18N_ar.properties | 7 +++++++ .../src/main/resources/assets/lang/I18N_de.properties | 11 +++++++++-- .../src/main/resources/assets/lang/I18N_es.properties | 10 +++++++++- .../src/main/resources/assets/lang/I18N_ja.properties | 11 +++++++++-- .../main/resources/assets/lang/I18N_lzh.properties | 7 +++++++ .../src/main/resources/assets/lang/I18N_ru.properties | 9 ++++++++- .../src/main/resources/assets/lang/I18N_uk.properties | 9 ++++++++- 7 files changed, 57 insertions(+), 7 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N_ar.properties b/HMCL/src/main/resources/assets/lang/I18N_ar.properties index 05c9feb2b8a..e9122e1a917 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -431,6 +431,7 @@ extension.mod=ملف مود extension.modloader.installer=مثبّت محمّل المودات extension.resourcepack=أرشيف حزمة الموارد extension.schematic=ملف مخطط +# extension.shaderpack= extension.world=أرشيف العالم fatal.create_hmcl_current_directory_failure=لا يستطيع HMCL إنشاء مجلد HMCL (%s). يُرجى نقل HMCL إلى موقع آخر وإعادة تشغيله. @@ -1637,7 +1638,13 @@ settings.type.global.manage=الإعدادات العامة # settings.type.global.preset.auto_name= shaderpack=الظلال +# shaderpack.add= +# shaderpack.add.failed= +# shaderpack.delete.failed= shaderpack.download.title=تنزيل ظلال - %1s +# shaderpack.loader.aperture= +# shaderpack.loader.optifine_iris= +# shaderpack.manage= system.architecture=المعمارية diff --git a/HMCL/src/main/resources/assets/lang/I18N_de.properties b/HMCL/src/main/resources/assets/lang/I18N_de.properties index 6d474be5f7b..e2fdedc4f6a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_de.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_de.properties @@ -188,6 +188,7 @@ addon.dependency.broken=Fehlerhafte Abhängigkeiten (Dieses Addon existierte vor addon.download.recommend=Empfohlene Version für Minecraft %1s addon.download.title.release=Minecraft %s addon.download.title.snapshot=Minecraft %s (Snapshots) +addon.install=Installieren addon.modrinth=Modrinth addon.sort.date_created=Erstellungsdatum addon.sort.last_updated=Zuletzt aktualisiert @@ -380,7 +381,6 @@ datetime.format=dd.MM.yyyy, HH\:mm\:ss download=Herunterladen download.code.404=Datei "%s" wurde nicht auf dem Remote-Server gefunden. download.content=Addons -download.shader=Shaderpakete download.failed=Fehler beim Herunterladen von "%1$s", Antwortcode: %2$d. download.failed.empty=Es sind keine Versionen verfügbar. Bitte klicken Sie hier, um zurückzukehren. download.failed.no_code=Herunterladen fehlgeschlagen @@ -428,6 +428,7 @@ extension.mod=Mod-Datei extension.modloader.installer=Modloader-Installationsprogramm extension.resourcepack=Ressourcenpaket-Archiv extension.schematic=Schematik-Datei +# extension.shaderpack= extension.world=Welt-Archiv fatal.create_hmcl_current_directory_failure=HMCL kann das HMCL-Verzeichnis (%s) nicht erstellen. Bitte verschieben Sie HMCL an einen anderen Speicherort und öffnen Sie es erneut. @@ -1180,7 +1181,6 @@ mods.restore=Wiederherstellen mods.url=Offizielle Seite mods.update_modpack_mod.warning=Das Aktualisieren von Mods in einem Modpack kann zu irreparablen Ergebnissen führen, wodurch das Modpack möglicherweise beschädigt wird, sodass es nicht mehr gestartet werden kann. Sind Sie sicher, dass Sie aktualisieren möchten? mods.warning.loader_mismatch=Modloader stimmt nicht überein -mods.install=Installieren mods.save_as=Speichern als mods.unknown=Unbekannte Mod @@ -1639,7 +1639,14 @@ settings.type.global.preset.remove.confirm=Möchten Sie die Voreinstellung "%s" settings.type.global.preset.subtitle=Die aktuelle Instanz erbt die Spieleinstellungen von der angegebenen Voreinstellung settings.type.global.preset.auto_name=Voreinstellung %d +# shaderpack= +# shaderpack.add= +# shaderpack.add.failed= +# shaderpack.delete.failed= shaderpack.download.title=Shaderpaket herunterladen - %1s +# shaderpack.loader.aperture= +# shaderpack.loader.optifine_iris= +# shaderpack.manage= system.architecture=Architektur diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index b80e469528a..8c6698ac646 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -188,6 +188,7 @@ addon.curseforge=CurseForge addon.download.recommend=Versión recomendada - Minecraft %1s # addon.download.title.release= # addon.download.title.snapshot= +# addon.install= addon.modrinth=Modrinth addon.sort.date_created=Fecha de creación addon.sort.last_updated=Última actualización @@ -428,6 +429,7 @@ extension.mod=Archivo mod # extension.modloader.installer= # extension.resourcepack= # extension.schematic= +# extension.shaderpack= extension.world=Archivo del mundo fatal.create_hmcl_current_directory_failure=El lanzador no puede crear el directorio HMCL (%s). Por favor, mueva el lanzador a otra ubicación y vuelva a abrirlo. @@ -1627,9 +1629,15 @@ settings.type.global.manage=Config. Global # settings.type.global.preset.subtitle= # settings.type.global.preset.auto_name= +shaderpack=Sombreadores +# shaderpack.add= +# shaderpack.add.failed= +# shaderpack.delete.failed= # shaderpack.download.title= +# shaderpack.loader.aperture= +# shaderpack.loader.optifine_iris= +# shaderpack.manage= -shaderpack=Sombreadores system.architecture=Arquitectura system.operating_system=Sistema operativo diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index 0d3ed8ac109..7ff302072ec 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -177,6 +177,7 @@ addon.curseforge=CurseForge # addon.download.recommend= # addon.download.title.release= # addon.download.title.snapshot= +# addon.install= addon.modrinth=Modrinth addon.sort.date_created=作成日 addon.sort.last_updated=最終更新 @@ -412,6 +413,7 @@ extension.mod=Modファイル # extension.modloader.installer= # extension.resourcepack= # extension.schematic= +# extension.shaderpack= extension.world=World zip # fatal.create_hmcl_current_directory_failure= @@ -980,7 +982,6 @@ mods.restore=Restore mods.url=公式ページ mods.update_modpack_mod.warning=modpack 内の mod を更新すると、修復不可能な結果につながる可能性があり、modpack が破損して起動できなくなる可能性があります。更新してもよろしいですか? # mods.warning.loader_mismatch= -# mods.install= # mods.save_as= # mods.unknown= @@ -1432,9 +1433,15 @@ settings.type.global.manage=グローバルゲーム設定 # settings.type.global.preset.subtitle= # settings.type.global.preset.auto_name= +shaderpack=シェーダー +# shaderpack.add= +# shaderpack.add.failed= +# shaderpack.delete.failed= # shaderpack.download.title= +# shaderpack.loader.aperture= +# shaderpack.loader.optifine_iris= +# shaderpack.manage= -shaderpack=シェーダー system.architecture=Arch system.operating_system=OS diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index eead6ce593f..a6b1957d89d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -425,6 +425,7 @@ extension.mod=改囊案 # extension.modloader.installer= # extension.resourcepack= # extension.schematic= +# extension.shaderpack= extension.world=生界緊囊 fatal.create_hmcl_current_directory_failure=HMCL 不能立其案夾 (%s),宜稍遷而再啟之。\n君可求助於 https://docs.hmcl.net/help.html。 @@ -1445,7 +1446,13 @@ settings.type.global.manage=全例戲設 # settings.type.global.preset.auto_name= shaderpack=光影 +# shaderpack.add= +# shaderpack.add.failed= +# shaderpack.delete.failed= shaderpack.download.title=引光影 - %1s +# shaderpack.loader.aperture= +# shaderpack.loader.optifine_iris= +# shaderpack.manage= system.architecture=大構 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 8d5b8d4dd87..85dac03475e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -428,6 +428,7 @@ extension.mod=Файл мода # extension.modloader.installer= # extension.resourcepack= # extension.schematic= +# extension.shaderpack= extension.world=Архив мира fatal.create_hmcl_current_directory_failure=Лаунчер не может создать папку HMCL (%s). Пожалуйста, переместите лаунчер в другое место и откройте его снова. @@ -1624,9 +1625,15 @@ settings.type.global.manage=Глобальные настройки # settings.type.global.preset.subtitle= # settings.type.global.preset.auto_name= +shaderpack=Шейдеры +# shaderpack.add= +# shaderpack.add.failed= +# shaderpack.delete.failed= # shaderpack.download.title= +# shaderpack.loader.aperture= +# shaderpack.loader.optifine_iris= +# shaderpack.manage= -shaderpack=Шейдеры system.architecture=Архитектура system.operating_system=Операционная система diff --git a/HMCL/src/main/resources/assets/lang/I18N_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index d410fe8fbe9..ed9c712d206 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -424,6 +424,7 @@ extension.mod=Файл мода extension.modloader.installer=Встановлювач завантажувача модів extension.resourcepack=Архів пакету ресурсів extension.schematic=Файл схеми +# extension.shaderpack= extension.world=Архів світу fatal.create_hmcl_current_directory_failure=Hello Minecraft! Лаунчер не може створити каталог HMCL (%s). Перемістіть HMCL в інше місце та відкрийте його знову. @@ -1591,9 +1592,15 @@ settings.type.global.preset.remove.confirm=Ви впевнені, що хоче settings.type.global.preset.subtitle=Поточний екземпляр успадковуватиме налаштування гри з вказаного пресету settings.type.global.preset.auto_name=Пресет %d +shaderpack=Шейдери +# shaderpack.add= +# shaderpack.add.failed= +# shaderpack.delete.failed= shaderpack.download.title=Завантаження шейдера - %1s +# shaderpack.loader.aperture= +# shaderpack.loader.optifine_iris= +# shaderpack.manage= -shaderpack=Шейдери system.architecture=Архітектура system.operating_system=Операційна система From d3d84bc96bdc28e7bfdfa86a0c1e75bd65b415ab Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 12 Sep 2026 17:57:27 +0800 Subject: [PATCH 34/36] update --- .../org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java | 6 +++--- .../org/jackhuang/hmcl/addon/shader/ShaderPackFile.java | 2 +- .../org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java | 2 +- .../org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java index 2db2e8933b3..40db3259780 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java @@ -234,7 +234,7 @@ public ShaderPackFile getFile() { } private Image getDefaultIcon() { - return switch (file.getLoaderType()) { + return switch (file.getShaderLoaderType()) { case OPTIFINE_IRIS -> FXUtils.newBuiltinImage("/assets/img/opti-iris.png"); default -> FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"); }; @@ -457,7 +457,7 @@ protected void updateControl(ShaderPackInfoObject item, boolean empty) { content.getTags().clear(); content.setTitle(file.getFileName()); content.setSubtitle(file.getMeta() == null || file.getMeta().name() == null ? "" : file.getMeta().name()); - content.addTag(switch (file.getLoaderType()) { + content.addTag(switch (file.getShaderLoaderType()) { case OPTIFINE_IRIS -> i18n("shaderpack.loader.optifine_iris"); case APERTURE -> i18n("shaderpack.loader.aperture"); }); @@ -488,7 +488,7 @@ public ShaderPackInfoDialog(ShaderPackInfoObject infoObject) { title.getTitleLabel().setWrapText(true); title.setTitle(shaderPackFile.getFileName()); title.setSubtitle(shaderPackFile.getFile().getFileName().toString()); - title.addTag(switch (shaderPackFile.getLoaderType()) { + title.addTag(switch (shaderPackFile.getShaderLoaderType()) { case OPTIFINE_IRIS -> i18n("shaderpack.loader.optifine_iris"); case APERTURE -> i18n("shaderpack.loader.aperture"); }); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFile.java index bbbf1cf8ad4..651843ec288 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFile.java @@ -87,7 +87,7 @@ public String getFileNameWithExtension() { return fileNameWithExtension; } - public ShaderLoaderType getLoaderType() { + public ShaderLoaderType getShaderLoaderType() { return loaderType; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java index ec80ba6ab3f..307061e0ad0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackFolder.java @@ -71,7 +71,7 @@ private ShaderPackFolder(ShaderPackManager manager, Path file, ShaderLoaderType } public @Nullable Image loadIcon() { - Path iconPath = switch (getLoaderType()) { + Path iconPath = switch (getShaderLoaderType()) { case APERTURE -> getFile().resolve("pack.png"); case OPTIFINE_IRIS -> getFile().resolve("shaders").resolve("pack.png"); }; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java index 9f614b44ef5..0942ee87fbd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackZipFile.java @@ -117,6 +117,7 @@ private ShaderPackZipFile(ShaderPackManager manager, Path file, String iconPath, if (currentVersion.isEmpty()) return null; List remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().projectId()) .filter(version -> version.gameVersions().contains(gameVersion)) //TODO loader + .filter(version -> version.loaders().stream().anyMatch(it -> it.type() == getShaderLoaderType())) .filter(version -> version.datePublished().compareTo(currentVersion.get().datePublished()) > 0) .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) .toList(); From 6f432ba554549be9760d792d6b85abd9c143adad Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 12 Sep 2026 17:57:58 +0800 Subject: [PATCH 35/36] update --- .../java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java index 6c95b1e2d94..7c9f93f7ec2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/shader/ShaderPackMeta.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.addon.shader; import com.google.gson.annotations.SerializedName; -import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jetbrains.annotations.Nullable; From 60331f5aebb5dd74ba7baebc38cc25b8b1d8ec19 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 12 Sep 2026 18:03:01 +0800 Subject: [PATCH 36/36] update --- .../org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java | 4 ++-- .../hmcl/addon/pack/resourcepack/ResourcePackFile.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java index 40db3259780..7049ecd7899 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ShaderPackListPage.java @@ -456,12 +456,12 @@ protected void updateControl(ShaderPackInfoObject item, boolean empty) { content.getTags().clear(); content.setTitle(file.getFileName()); - content.setSubtitle(file.getMeta() == null || file.getMeta().name() == null ? "" : file.getMeta().name()); + content.setSubtitle(file.getMeta().description() == null ? "" : file.getMeta().description()); content.addTag(switch (file.getShaderLoaderType()) { case OPTIFINE_IRIS -> i18n("shaderpack.loader.optifine_iris"); case APERTURE -> i18n("shaderpack.loader.aperture"); }); - if (file.getMeta() != null && file.getMeta().version() != null) + if (file.getMeta().version() != null) content.addTag(file.getMeta().version()); FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager")); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java index 773e7f2192e..9af1033102c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/pack/resourcepack/ResourcePackFile.java @@ -36,7 +36,8 @@ public sealed abstract class ResourcePackFile extends LocalAddonFile implements Comparable permits ResourcePackFolder, ResourcePackZipFile { - static ResourcePackFile fromFile(ResourcePackManager manager, Path path) { + static @Nullable ResourcePackFile fromFile(ResourcePackManager manager, Path path) { + if (!isFileResourcePack(path)) return null; return Files.isRegularFile(path) ? ResourcePackZipFile.load(manager, path) : ResourcePackFolder.load(manager, path); }