From 865821379f6fd8c4366d54b7971ef441c8a34600 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 4 Jul 2026 14:00:28 +0800 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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