Skip to content
Open
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
652491c
修复资源包管理界面边框样式问题
Calboot Dec 14, 2025
2bc3fa7
修复 #4680
Calboot Dec 14, 2025
c5406e3
update
Calboot Dec 14, 2025
facdd47
修复光影包界面下载标题错误
Calboot Dec 14, 2025
d24b7b2
资源包启用/禁用功能
Calboot Dec 16, 2025
a2b5e8b
同步模组管理界面 Part1
Calboot Dec 16, 2025
2ca2f3e
为不兼容资源包添加警告
Calboot Dec 16, 2025
ce9377b
update
Calboot Dec 17, 2025
bd96337
启用/禁用资源包时添加警告
Calboot Dec 17, 2025
bc229e3
资源包详细信息界面
Calboot Dec 17, 2025
777aea3
修复esc键失效
Calboot Dec 17, 2025
e7c4f2e
Update i18n
Calboot Dec 17, 2025
07a9cc0
Update i18n
Calboot Dec 17, 2025
9aca021
完善资源包版本解析
Calboot Dec 18, 2025
eacf26b
Merge remote-tracking branch 'origin/resourcepack-enhancement' into r…
Calboot Dec 18, 2025
8d44424
从模组管理界面同步
Calboot Dec 18, 2025
00b377a
update
Calboot Dec 18, 2025
7d2ea79
update i18n
Calboot Dec 19, 2025
8ba09cf
移除资源包删除按钮
Calboot Dec 20, 2025
ae789fb
Merge branch 'HMCL-dev:main' into resourcepack-enhancement
Calboot Dec 20, 2025
9f6f42b
添加cf/modrinth页面检测 & fix i18n
Calboot Dec 20, 2025
9172c53
Apply suggestions
Calboot Dec 20, 2025
7ab6e62
update i18n
Calboot Dec 20, 2025
c7010f9
Apply suggestions from code review
Calboot Dec 21, 2025
b0fca8d
Apply suggestions from code review
Calboot Dec 21, 2025
78b5ce7
资源包更新功能
Calboot Dec 21, 2025
6709fbe
update
Calboot Dec 21, 2025
07632bb
update
Calboot Dec 21, 2025
6a6a07d
update
Calboot Dec 21, 2025
827272e
update
Calboot Dec 21, 2025
07a7b34
sync with #5016
Calboot Dec 21, 2025
4b69a17
Merge branch 'HMCL-dev:main' into resourcepack-enhancement
Calboot Dec 22, 2025
60ea149
update
Calboot Dec 23, 2025
e944351
update
Calboot Dec 23, 2025
29c34b9
update
Calboot Dec 24, 2025
b42dac6
update
Calboot Dec 24, 2025
bdc6cdb
Merge remote-tracking branch 'upstream/main' into resourcepack-enhanc…
Calboot Dec 24, 2025
3204857
update
Calboot Dec 24, 2025
423a021
update
Calboot Dec 24, 2025
194ce5f
Apply suggestions
Calboot Dec 24, 2025
79c04a1
update
Calboot Dec 25, 2025
b87dd5a
update
Calboot Dec 25, 2025
ff7f597
update
Calboot Dec 25, 2025
a915024
update
Calboot Dec 25, 2025
e1d8f5f
using config
Calboot Dec 26, 2025
f108671
update
Calboot Dec 26, 2025
87bb56d
update
Calboot Dec 26, 2025
4951406
添加资源包、光影包等的推荐下载
Calboot Dec 26, 2025
59d0dec
update
Calboot Dec 26, 2025
186ae00
update
Calboot Dec 27, 2025
cf84e5f
update
Calboot Dec 27, 2025
97d7d0f
update
Calboot Dec 27, 2025
4bf35f2
update
Calboot Dec 27, 2025
4fdab0a
update
Calboot Dec 28, 2025
ca167cb
update
Calboot Dec 28, 2025
6ab182c
update
Calboot Dec 28, 2025
c3ae0c1
update
Calboot Dec 28, 2025
d9a9dbb
update
Calboot Dec 28, 2025
701b839
清理代码
Calboot Dec 28, 2025
73662a6
Apply suggestions
Calboot Jan 1, 2026
dab739d
Merge branch 'HMCL-dev:main' into resourcepack-enhancement
Calboot Jan 1, 2026
9ddc2ed
Merge branch 'main' into resourcepack-enhancement
Calboot Jan 2, 2026
c4b4d2f
update
Calboot Jan 2, 2026
6fd1a56
update
Calboot Jan 3, 2026
106b5bb
update
Calboot Jan 3, 2026
0c9559c
Merge branch 'main' into resourcepack-enhancement
Calboot Jan 4, 2026
add29f8
update
Calboot Jan 4, 2026
598fd23
Merge branch 'main' into resourcepack-enhancement
Calboot Jan 13, 2026
7765f0c
update
Calboot Jan 13, 2026
26282ef
update
Calboot Jan 18, 2026
def0840
Merge branch 'main' into resourcepack-enhancement
Calboot Jan 18, 2026
3ecedb0
完成合并
Calboot Jan 18, 2026
1329d63
跟紧主线
Calboot Jan 18, 2026
c0e8395
Merge branch 'main' into resourcepack-enhancement
Calboot Jan 19, 2026
542b2e8
update
Calboot Jan 23, 2026
75ce692
update
Calboot Jan 23, 2026
c727451
update
Calboot Jan 24, 2026
2bf8151
Update HMCL/src/main/resources/assets/lang/I18N_zh.properties
Calboot Jan 24, 2026
d6a8d14
Update HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
Calboot Jan 24, 2026
89e81d1
Merge remote-tracking branch 'origin/resourcepack-enhancement' into r…
Calboot Jan 24, 2026
23c8c5e
update
Calboot Jan 24, 2026
fe05cca
update
Calboot Jan 28, 2026
0e96a9d
Apply AI suggestions
Calboot Jan 29, 2026
05e60d6
Merge branch 'main' into resourcepack-enhancement
Calboot Jan 30, 2026
6a7f453
Merge
Calboot Feb 1, 2026
72d9ca3
Delete HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Resourcepack…
Calboot Feb 1, 2026
5f864a9
Fix #5207
Calboot Feb 1, 2026
871bd87
Merge branch 'main' into resourcepack-enhancement
Calboot Feb 2, 2026
63f93d7
update
Calboot Feb 3, 2026
774c2b7
Merge
Calboot Feb 6, 2026
4f0fa24
update
Calboot Feb 6, 2026
1bee3f7
update
Calboot Feb 6, 2026
063ff8c
update
Calboot Feb 6, 2026
1ed3dbe
Merge
Calboot Feb 7, 2026
e9cc88e
Merge branch 'HMCL-dev:main' into resourcepack-enhancement
Calboot Feb 8, 2026
c60634e
update
Calboot Feb 8, 2026
e5a3870
update
Calboot Feb 8, 2026
b54ae5d
Apply suggestions
Calboot Feb 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package org.jackhuang.hmcl.game;

import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.ui.versions.ModTranslations;
Expand Down Expand Up @@ -110,8 +109,8 @@ public Stream<Category> getCategories() throws IOException {
}

@Override
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(localModFile, file);
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(file);
}

@Override
Expand Down
11 changes: 11 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ public ObservableSet<String> getDisabledJava() {
return disabledJava;
}

@SerializedName("resourcePackWarningShown")
private final BooleanProperty resourcePackWarningShown = new SimpleBooleanProperty(false);

public boolean isResourcePackWarningShown() {
return resourcePackWarningShown.get();
}

public void onResourcePackWarningShown() {
resourcePackWarningShown.set(true);
}

static final class Adapter extends ObservableSetting.Adapter<GlobalConfig> {
@Override
protected GlobalConfig createInstance() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;

public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_MOD =
(profile, version, file) -> download(profile, version, file, "mods");
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_RESOURCE_PACK =
(profile, version, file) -> download(profile, version, file, "resourcepacks");
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_SHADER =
(profile, version, file) -> download(profile, version, file, "shaderpacks");

private final ReadOnlyObjectWrapper<DecoratorPage.State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1));
private final TabHeader tab;
private final TabHeader.Tab<VersionsPage> newGameTab = new TabHeader.Tab<>("newGameTab");
Expand Down Expand Up @@ -94,9 +101,9 @@ public DownloadPage(String uploadVersion) {
page.getActions().add(installLocalModpackButton);
return page;
}));
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((profile, version, file) -> download(profile, version, file, "mods"), true)));
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, file) -> download(profile, version, file, "resourcepacks"), true)));
shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, (profile, version, file) -> download(profile, version, file, "shaderpacks"), true)));
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod(FOR_MOD, true)));
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack(FOR_RESOURCE_PACK, true)));
shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, FOR_SHADER, true)));
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));
tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,39 @@
*/
package org.jackhuang.hmcl.ui.versions;

import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.LocalAddonFile;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.task.Task;

import java.util.*;
import java.util.stream.Collectors;

public class ModCheckUpdatesTask extends Task<List<LocalModFile.ModUpdate>> {
private final String gameVersion;
private final Collection<LocalModFile> mods;
private final Collection<Collection<Task<LocalModFile.ModUpdate>>> dependents;

public ModCheckUpdatesTask(String gameVersion, Collection<LocalModFile> mods) {
this.gameVersion = gameVersion;
this.mods = mods;
public class CheckUpdatesTask<T extends LocalAddonFile> extends Task<List<LocalAddonFile.ModUpdate>> {
private final Collection<Collection<Task<LocalAddonFile.ModUpdate>>> dependents;

public CheckUpdatesTask(String gameVersion, Collection<T> mods, RemoteModRepository.Type repoType) {
Map<String, RemoteModRepository> repos = new LinkedHashMap<>(2);
for (RemoteMod.Type modType : RemoteMod.Type.values()) {
RemoteModRepository repo = modType.getRepoForType(repoType);
if (repo != null) {
repos.put(modType.name(), repo);
}
}
dependents = mods.stream()
.map(mod ->
Arrays.stream(RemoteMod.Type.values())
.map(type ->
Task.supplyAsync(() -> mod.checkUpdates(gameVersion, type.getRemoteModRepository()))
repos.entrySet().stream()
.map(entry ->
Task.supplyAsync(() -> mod.checkUpdates(gameVersion, entry.getValue()))
.setSignificance(TaskSignificance.MAJOR)
.setName(String.format("%s (%s)", mod.getFileName(), type.name())).withCounter("update.checking")
.setName(String.format("%s (%s)", mod.getFileName(), entry.getKey())).withCounter("update.checking")
)
.collect(Collectors.toList())
)
.collect(Collectors.toList());

setStage("update.checking");
getProperties().put("total", dependents.size() * RemoteMod.Type.values().length);
getProperties().put("total", dependents.size() * repos.size());
}

@Override
Expand Down Expand Up @@ -75,8 +78,8 @@ public void execute() throws Exception {
.map(tasks -> tasks.stream()
.filter(task -> task.getResult() != null)
.map(Task::getResult)
.filter(modUpdate -> !modUpdate.getCandidates().isEmpty())
.max(Comparator.comparing((LocalModFile.ModUpdate modUpdate) -> modUpdate.getCandidates().get(0).getDatePublished()))
.filter(modUpdate -> !modUpdate.candidates().isEmpty())
.max(Comparator.comparing((LocalAddonFile.ModUpdate modUpdate) -> modUpdate.candidates().get(0).getDatePublished()))
.orElse(null)
)
.filter(Objects::nonNull)
Expand Down
74 changes: 44 additions & 30 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,20 @@ public class DownloadPage extends Control implements DecoratorPage {
private final Profile.ProfileVersion version;
private final DownloadCallback callback;
private final DownloadListPage page;
private final RemoteModRepository.Type type;

private SimpleMultimap<String, RemoteMod.Version, List<RemoteMod.Version>> versions;

public DownloadPage(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
this(page, addon, version, callback, null);
}

public DownloadPage(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback, @Nullable RemoteModRepository.Type type) {
this.page = page;
this.repository = page.repository;
this.addon = addon;
this.translations = ModTranslations.getTranslationsByRepositoryType(repository.getType());
this.type = Objects.requireNonNullElse(type, repository.getType());
this.translations = ModTranslations.getTranslationsByRepositoryType(this.type);
this.mod = translations.getModByCurseForgeId(addon.getSlug());
this.version = version;
this.callback = callback;
Expand Down Expand Up @@ -283,14 +289,22 @@ protected ModDownloadPageSkin(DownloadPage control) {

resolve:
for (RemoteMod.Version modVersion : modVersions) {
for (ModLoaderType loader : modVersion.getLoaders()) {
if (targetLoaders.contains(loader)) {
list.getContent().addAll(
ComponentList.createComponentListTitle(i18n("mods.download.recommend", gameVersion)),
new ModItem(modVersion, control)
);
break resolve;
if (getSkinnable().type == RemoteModRepository.Type.MOD) {
for (ModLoaderType loader : modVersion.getLoaders()) {
if (targetLoaders.contains(loader)) {
list.getContent().addAll(
ComponentList.createComponentListTitle(i18n("mods.download.recommend", gameVersion)),
new ModItem(modVersion, control)
);
break resolve;
}
}
} else {
list.getContent().addAll(
ComponentList.createComponentListTitle(i18n("mods.download.recommend", gameVersion)),
new ModItem(modVersion, control)
);
break;
}
}
}
Expand Down Expand Up @@ -335,7 +349,7 @@ private static final class DependencyModItem extends StackPane {
Pair.pair(RemoteMod.DependencyType.BROKEN, "mods.dependency.broken")
));

DependencyModItem(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, DownloadCallback callback) {
DependencyModItem(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version) {
HBox pane = new HBox(8);
pane.setPadding(new Insets(0, 8, 0, 8));
pane.setAlignment(Pos.CENTER_LEFT);
Expand All @@ -346,15 +360,24 @@ private static final class DependencyModItem extends StackPane {
imageView.setFitHeight(40);
pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);

RemoteModRepository.Type type = addon.getRepositoryType();

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 -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_SHADER;
default -> null;
};

RipplerContainer container = new RipplerContainer(pane);
FXUtils.onClicked(container, () -> {
fireEvent(new DialogCloseEvent());
Controllers.navigate(new DownloadPage(page, addon, version, callback));
Controllers.navigate(new DownloadPage(page, addon, version, callback, type));
});
getChildren().setAll(container);

if (addon != RemoteMod.BROKEN) {
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(type).getModByCurseForgeId(addon.getSlug());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.getTitle());
content.setSubtitle(addon.getDescription());
addon.getCategories().stream()
Expand Down Expand Up @@ -445,24 +468,15 @@ private static final class ModItem extends StackPane {

private static final class ModVersion extends JFXDialogLayout {
public ModVersion(RemoteMod.Version version, DownloadPage selfPage) {
RemoteModRepository.Type type = selfPage.repository.getType();

String title;
switch (type) {
case WORLD:
title = "world.download.title";
break;
case MODPACK:
title = "modpack.download.title";
break;
case RESOURCE_PACK:
title = "resourcepack.download.title";
break;
case MOD:
default:
title = "mods.download.title";
break;
}
RemoteModRepository.Type type = selfPage.type;

String title = switch (type) {
case WORLD -> "world.download.title";
case MODPACK -> "modpack.download.title";
case RESOURCE_PACK -> "resourcepack.download.title";
case SHADER -> "download.shader.title";
default -> "mods.download.title";
};
this.setHeading(new HBox(new Label(i18n(title, version.getName()))));

VBox box = new VBox(8);
Expand Down Expand Up @@ -538,7 +552,7 @@ private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage,
list.add(title);
dependencies.put(dependency.getType(), list);
}
DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback);
DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version);
dependencies.get(dependency.getType()).add(dependencyModItem);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.ModLoaderType;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
Expand Down Expand Up @@ -103,8 +104,8 @@ private void loadMods(ModManager modManager) {
CompletableFuture.supplyAsync(() -> {
lock.lock();
try {
modManager.refreshMods();
return modManager.getMods().stream().map(ModListPageSkin.ModInfoObject::new).toList();
modManager.refresh();
return modManager.getLocalFiles().stream().map(ModListPageSkin.ModInfoObject::new).toList();
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
Expand Down Expand Up @@ -234,7 +235,7 @@ public void checkUpdates() {
.composeAsync(() -> {
Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);
if (gameVersion.isPresent()) {
return new ModCheckUpdatesTask(gameVersion.get(), modManager.getMods());
return new CheckUpdatesTask<>(gameVersion.get(), modManager.getLocalFiles(), RemoteModRepository.Type.MOD);
}
return null;
})
Expand All @@ -244,7 +245,7 @@ public void checkUpdates() {
} else if (result.isEmpty()) {
Controllers.dialog(i18n("mods.check_updates.empty"));
} else {
Controllers.navigateForward(new ModUpdatesPage(modManager, result));
Controllers.navigateForward(new UpdatesPage<>(modManager, result));
}
})
.withStagesHint(Collections.singletonList("update.checking")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ final class ModInfoDialog extends JFXDialogLayout {
RemoteModRepository repository = item.getValue();
JFXHyperlink button = new JFXHyperlink(i18n(item.getKey()));
Task.runAsync(() -> {
Optional<RemoteMod.Version> versionOptional = repository.getRemoteVersionByLocalFile(modInfo.getModInfo(), modInfo.getModInfo().getFile());
Optional<RemoteMod.Version> versionOptional = repository.getRemoteVersionByLocalFile(modInfo.getModInfo().getFile());
if (versionOptional.isPresent()) {
RemoteMod remoteMod = repository.getModById(versionOptional.get().getModid());
FXUtils.runInFX(() -> {
Expand Down Expand Up @@ -493,7 +493,7 @@ final class ModInfoDialog extends JFXDialogLayout {
repository instanceof CurseForgeRemoteModRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false),
remoteMod,
new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getInstanceId()),
(profile, version, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(profile, version, file, "mods")
org.jackhuang.hmcl.ui.download.DownloadPage.FOR_MOD
));
});
button.setDisable(false);
Expand Down
Loading