Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8658213
fix ReShaded download
ToobLac Jul 4, 2026
6a3ab61
framework
ToobLac Jul 4, 2026
48ae4dd
optimize resource pack meta loading
ToobLac Jul 4, 2026
5656161
update
ToobLac Jul 4, 2026
a06a481
update
ToobLac Jul 4, 2026
1f4e7fa
update
ToobLac Jul 5, 2026
41c4221
update
ToobLac Jul 5, 2026
a8ee78f
update
ToobLac Jul 5, 2026
05099f7
update
ToobLac Jul 5, 2026
28ac689
loading
ToobLac Jul 5, 2026
88b0aee
roughly usable
ToobLac Jul 6, 2026
d28ba7a
i18n
ToobLac Jul 6, 2026
a5108ca
i18n
ToobLac Jul 6, 2026
6434697
meta
ToobLac Jul 6, 2026
3ea42ed
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Jul 11, 2026
a60802b
record
ToobLac Jul 16, 2026
7852910
update
ToobLac Jul 16, 2026
47bc368
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Jul 25, 2026
a00b99b
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Jul 27, 2026
a213da4
update
ToobLac Jul 29, 2026
fedb358
update
ToobLac Jul 29, 2026
c0db182
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Jul 29, 2026
dfb4409
update
ToobLac Jul 29, 2026
abe1fee
update
ToobLac Jul 29, 2026
3941a80
update
ToobLac Jul 29, 2026
22c6f48
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Aug 2, 2026
94bf394
update
ToobLac Aug 2, 2026
77f55d8
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Aug 6, 2026
c8251a2
update
ToobLac Aug 6, 2026
5cf5f25
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Aug 19, 2026
c311122
update
ToobLac Aug 19, 2026
983a407
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Aug 20, 2026
7fe795a
update
ToobLac Aug 20, 2026
28f36b0
update
ToobLac Aug 22, 2026
a71c5b1
update
ToobLac Aug 29, 2026
10c6fe4
update
ToobLac Aug 29, 2026
9744571
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Sep 8, 2026
7cf44cb
update
ToobLac Sep 9, 2026
dbbd6d5
prepare for merging
ToobLac Sep 10, 2026
d86d8e2
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Sep 10, 2026
cb7a267
complete merging
ToobLac Sep 10, 2026
17ba204
aperture & fix config rename
ToobLac Sep 12, 2026
6b2ddef
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Sep 12, 2026
c7a42ff
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Sep 12, 2026
f70dba3
sync translation
ToobLac Sep 12, 2026
d3d84bc
update
ToobLac Sep 12, 2026
6f432ba
update
ToobLac Sep 12, 2026
60331f5
update
ToobLac Sep 12, 2026
f6121e7
Merge remote-tracking branch 'upstream/main' into shaders
ToobLac Sep 14, 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 @@ -59,7 +59,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion

int count = 0;
for (ModTranslations.Mod mod : ModTranslations.getTranslationsByAddonType(getType()).searchMod(searchFilter)) {
String englishSearchFilter = String.join(" ", StringUtils.tokenize(StringUtils.isNotBlank(mod.getSubname()) ? mod.getSubname() : mod.getName()));
String englishSearchFilter = String.join(" ", StringUtils.tokenize(StringUtils.isNotBlank(mod.subname()) ? mod.subname() : mod.name()));
if (StringUtils.isNotBlank(englishSearchFilter)) {
englishSearchFiltersSet.add(englishSearchFilter);
}
Expand Down Expand Up @@ -92,7 +92,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion
}

ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByAddonType(getType()).getModByCurseForgeId(remoteAddon.slug());
if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) {
if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.name()) && StringUtils.containsChinese(chineseTranslation.name())) {
searchResultArray[chineseIndex++] = remoteAddon;
} else {
searchResultArray[englishIndex--] = remoteAddon;
Expand All @@ -104,10 +104,10 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
return new SearchResult(Stream.concat(Arrays.stream(searchResultArray, 0, chineseIndex).map(remoteMod -> {
ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByAddonType(getType()).getModByCurseForgeId(remoteMod.slug());
if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) {
if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.name()) || !StringUtils.containsChinese(chineseRemoteMod.name())) {
return Pair.pair(remoteMod, Integer.MAX_VALUE);
}
String chineseRemoteModName = chineseRemoteMod.getName();
String chineseRemoteModName = chineseRemoteMod.name();
if (searchFilter.isEmpty() || chineseRemoteModName.isEmpty()) {
return Pair.pair(remoteMod, Math.max(searchFilter.length(), chineseRemoteModName.length()));
}
Expand Down
29 changes: 14 additions & 15 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,6 @@
import static org.jackhuang.hmcl.util.logging.Logger.LOG;

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

private final ReadOnlyObjectWrapper<DecoratorPage.State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1));
private final TabHeader tab;
Expand All @@ -93,12 +87,12 @@ public DownloadPage() {
this(null);
}

public DownloadPage(GameInstanceID uploadInstance) {
public DownloadPage(GameInstanceID updateInstance) {
newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(),
GameComponentType.GAME, versionPageNavigator::onGameSelected)));
modpackTab.setNodeSupplier(loadVersionFor(() -> {
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((downloadProvider, repository, __, modpack, file) -> {
Instances.downloadModpackImpl(downloadProvider, repository, uploadInstance, modpack, file);
Instances.downloadModpackImpl(downloadProvider, repository, updateInstance, modpack, file);
}, false);

JFXButton installLocalModpackButton = FXUtils.newRaisedButton(i18n("install.modpack"));
Expand All @@ -107,9 +101,9 @@ public DownloadPage(GameInstanceID uploadInstance) {
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);

Expand All @@ -124,7 +118,7 @@ public DownloadPage(GameInstanceID uploadInstance) {
.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);
Expand Down Expand Up @@ -217,14 +211,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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@

import static org.jackhuang.hmcl.util.logging.Logger.LOG;

public class AddonCheckUpdatesTask<T extends LocalAddonFile> extends Task<List<LocalAddonFile.AddonUpdate>> {
public class AddonCheckUpdatesTask extends Task<List<LocalAddonFile.AddonUpdate>> {
private final DownloadProvider downloadProvider;
private final List<Task<LocalAddonFile.AddonUpdate>> dependents;

public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection<T> addons) {
public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection<? extends LocalAddonFile> addons) {
this.downloadProvider = downloadProvider;
dependents = addons.stream().map(addon ->
Task.supplyAsync(Schedulers.io(), () -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;

public class AddonUpdatesPage<F extends LocalAddonFile> extends BorderPane implements DecoratorPage {
public class AddonUpdatesPage extends BorderPane implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("addon.check_update")));

private final LocalAddonManager<F> localAddonManager;
private final Path addonsDirectory;
private final ObservableList<AddonUpdateObject> objects;

@SuppressWarnings("unchecked")
public AddonUpdatesPage(LocalAddonManager<F> localAddonManager, List<LocalAddonFile.AddonUpdate> updates) {
this.localAddonManager = localAddonManager;
public AddonUpdatesPage(Path addonsDirectory, List<LocalAddonFile.AddonUpdate> updates) {
this.addonsDirectory = addonsDirectory;

getStyleClass().add("gray-background");

Expand Down Expand Up @@ -150,7 +150,7 @@ private <T> void setupCellValueFactory(TableColumn<AddonUpdateObject, T> column,

private void updateFiles() {
AddonUpdateTask task = new AddonUpdateTask(
localAddonManager.getDirectory(),
addonsDirectory,
objects.stream()
.filter(AddonUpdateObject::isEnabled)
.map(AddonUpdateObject::getData)
Expand Down Expand Up @@ -395,19 +395,18 @@ public static class AddonUpdateTask extends Task<Void> {
RemoteAddon.Version remote = addon.targetVersion();
boolean isDisabled = local.isDisabled();
String originalFileName = local.getFile().getFileName().toString();
String fileName = remote.file().filename();
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;
})
Expand All @@ -418,11 +417,14 @@ public static class AddonUpdateTask extends Task<Void> {
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);
}
}
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public class DownloadListPage extends Control implements DecoratorPage {
private final ListProperty<RemoteAddon> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
private final ObservableList<GameInstanceID> instances = FXCollections.observableArrayList();
private final ObjectProperty<GameInstanceID> selectedInstance = new SimpleObjectProperty<>();
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<Node> actions = FXCollections.observableArrayList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@

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(@Nullable RemoteAddon.Type addonType) {
if (addonType == null) return null;
return switch (addonType) {
case MOD -> FOR_MOD;
case RESOURCE_PACK -> FOR_RESOURCE_PACK;
case SHADER_PACK -> FOR_SHADER;
default -> null;
};
}

private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
private final BooleanProperty loaded = new SimpleBooleanProperty(false);
private final BooleanProperty loading = new SimpleBooleanProperty(false);
Expand All @@ -69,7 +89,7 @@ public class DownloadPage extends Control implements DecoratorPage {
private final RemoteAddon addon;
private final ModTranslations.Mod mod;
private final HMCLGameInstance.Optional instanceReference;
private final DownloadCallback callback;
private final DownloadCallback callback; // ONLY FOR MODPACKS
private final DownloadListPage page;
private final RemoteAddon.Type type;

Expand Down Expand Up @@ -157,11 +177,17 @@ public void setFailed(boolean failed) {
this.failed.set(failed);
}

public @Nullable DownloadCallback getCallbackFor(RemoteAddon addon) {
if (addon.type() == RemoteAddon.Type.MODPACK) return this.callback;
return getDownloadCallbackFor(addon.type());
}

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);
}
}

Expand Down Expand Up @@ -386,20 +412,13 @@ private static final class DependencyAddonItem extends LineButton {
pane.getChildren().setAll(imageView, content);
FXUtils.setLimitHeight(this, 60);

RemoteAddon.Type type = addon.type();
DownloadCallback callback = type == null ? null : 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);

ModTranslations.Mod mod = ModTranslations.getTranslationsByAddonType(type).getModByCurseForgeId(addon.slug());
ModTranslations.Mod mod = ModTranslations.getTranslationsByAddonType(addon.type()).getModByCurseForgeId(addon.slug());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.title());
content.setSubtitle(addon.description());
for (String category : addon.categories()) {
Expand Down Expand Up @@ -483,7 +502,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) {
RemoteAddon.Type type = selfPage.type;

String title = switch (type) {
Expand All @@ -497,7 +516,7 @@ public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage s

VBox box = new VBox(8);
box.setPadding(new Insets(8));
var addonItem = new AddonItem(mod, version, selfPage);
var 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);

Expand Down Expand Up @@ -531,14 +550,14 @@ public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage s
this.setBody(box);

JFXButton downloadButton = null;
if (selfPage.callback != null) {
downloadButton = new JFXButton(type == RemoteAddon.Type.MODPACK ? i18n("install.modpack") : i18n("mods.install"));
if (selfPage.getCallbackFor(addon) != null) {
downloadButton = new JFXButton(type == RemoteAddon.Type.MODPACK ? i18n("install.modpack") : i18n("addon.install"));
downloadButton.getStyleClass().add("dialog-accept");
downloadButton.setOnAction(e -> {
if (type == RemoteAddon.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
fireEvent(new DialogCloseEvent());
}
selfPage.download(mod, version);
selfPage.download(addon, version);
});
}

Expand Down
Loading