Skip to content

[Feature] [Bugfix] 资源包有关优化与修复#4980

Open
Calboot wants to merge 124 commits intoHMCL-dev:mainfrom
Calboot:resourcepack-enhancement
Open

[Feature] [Bugfix] 资源包有关优化与修复#4980
Calboot wants to merge 124 commits intoHMCL-dev:mainfrom
Calboot:resourcepack-enhancement

Conversation

@Calboot
Copy link
Contributor

@Calboot Calboot commented Dec 14, 2025

  • 资源包管理界面边框样式修复
  • 修复资源包依赖模组下载错误的问题
  • 资源包启用/禁用功能
  • 资源包详细信息界面
  • 与模组管理界面同步
  • 更新资源包

具体改动

@Calboot
Copy link
Contributor Author

Calboot commented Dec 14, 2025

需要懂其他语言的人完善 i18n

@Calboot Calboot changed the title [Enhancement] 资源包有关优化与修复 [Enhancement] [Bugfix] 资源包有关优化与修复 Dec 14, 2025
Copy link
Contributor

@3gf8jv4dv 3gf8jv4dv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我在 CF 和 MR 分别下载了好几个资源包,结果都显示「游戏版本元数据缺失」。如果真的要为这个显示警告的话,可能很多人都会迷惑。

可能是我搞错了,我还在弄清楚这一块。

@3gf8jv4dv
Copy link
Contributor

我下载了 Translations for Sodium,放在 1.21.9~1.21.11 的资源包文件夹内,启动器会显示「游戏版本元数据缺失」。1.21.8 和 1.21.1 正常。不知道是不是 bug。

Calboot and others added 2 commits December 17, 2025 21:28
Co-authored-by: 3gf8jv4dv <[email protected]>
Co-authored-by: 3gf8jv4dv <[email protected]>
@Calboot
Copy link
Contributor Author

Calboot commented Dec 17, 2025

我下载了 Translations for Sodium,放在 1.21.9~1.21.11 的资源包文件夹内,启动器会显示「游戏版本元数据缺失」。1.21.8 和 1.21.1 正常。不知道是不是 bug。

确实是,我在修

@Calboot
Copy link
Contributor Author

Calboot commented Dec 18, 2025

我下载了 Translations for Sodium,放在 1.21.9~1.21.11 的资源包文件夹内,启动器会显示「游戏版本元数据缺失」。1.21.8 和 1.21.1 正常。不知道是不是 bug。

Solved

@Calboot Calboot marked this pull request as ready for review December 18, 2025 14:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

HMCLCore/src/main/java/org/jackhuang/hmcl/resourcepack/ResourcePackFile.java:27

  • isFileResourcePack treats any existing path ending with .zip as a resource pack, even if it is a directory without pack.mcmeta (e.g., an extracted folder named foo.zip). That will make parse(...) attempt to load it as a ResourcePackFolder and surface misleading warnings/log spam instead of skipping it. Consider requiring Files.isRegularFile(file) && name.endsWith(".zip") for zip packs, and Files.isDirectory(file) && Files.isRegularFile(file.resolve("pack.mcmeta")) for folder packs.
    HMCLCore/src/main/java/org/jackhuang/hmcl/resourcepack/ResourcePackManager.java:252
  • refresh() currently calls addResourcePackInfo(subitem) without per-entry exception handling. If a single broken/invalid zip triggers an IOException during parsing, the whole refresh fails and the UI ends up clearing the entire list (see ResourcePackListPage.refresh() exception path). Consider catching exceptions per file inside the loop, logging, and continuing so one bad pack doesn’t prevent listing the rest.
    HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java:301
  • AddonUpdateTask downloads the updated file using remote.getFile().getFilename() as the target name. For resource packs, Minecraft references enabled packs by filename in options.txt (e.g., resourcePacks=["file/<filename>"]), so if the remote filename differs from the current local filename, updating will silently disable the pack and leave a stale entry pointing to a non-existent file. Consider preserving the existing filename for resource packs (download to the old name), or updating options.txt to replace the old packId with the new filename after a successful update.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# Conflicts:
#	HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (5)

HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java:318

  • When restoring the state after a failed download (lines 315-317), the code calls local.setOld(false) and local.markDisabled() if the file was disabled. However, these operations can throw IOException which is not caught here. If setOld or markDisabled throws an exception, the restoration will fail silently and the file could be left in an inconsistent state. Consider wrapping these calls in a try-catch block or documenting that they shouldn't throw exceptions in this context.
    HMCLCore/src/main/java/org/jackhuang/hmcl/resourcepack/ResourcePackManager.java:118
  • The condition at line 118 uses a negation with OR which creates a logic error. The intent seems to be checking if NEITHER minPackVersion NOR maxPackVersion is unspecified (i.e., both are specified), but the condition !(A || B) is equivalent to !A && !B, which is correct. However, the comment "See https://zh.minecraft.wiki/w/Pack.mcmeta" and the subsequent logic suggest this might be handling a specific case.

The real issue is that the condition should probably be !minPackVersion.isUnspecified() && !maxPackVersion.isUnspecified() for clarity, rather than using De Morgan's law. The current form is harder to read and reason about.
HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java:272

  • The compileQuery method doesn't handle potential PatternSyntaxException that can be thrown by Pattern.compile(). If a user provides an invalid regex pattern after "regex:", the exception will propagate uncaught. This should either catch the exception and fall back to a literal string search, or document that callers must handle PatternSyntaxException.
    HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java:630
  • The broad catch (Exception e) in both serializeStringList and deserializeStringList silently swallows all exceptions and returns a default value. This makes debugging difficult when JSON serialization/deserialization fails. Consider logging the exception or being more specific about which exceptions to catch (e.g., JsonSyntaxException or JsonParseException).
    HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java:84
  • The isUnspecified() method checks if getMin().compareTo(getMax()) > 0, but getMin() and getMax() create new PackVersion objects from min and max fields. If either min or max is negative (which signals UNSPECIFIED), the first two conditions will catch it. However, the third condition seems redundant or potentially incorrect - it would be clearer to check the raw min and max values directly rather than creating PackVersion objects and comparing them.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 101 to 107
public static String getNameWithoutExtension(Path file) {
return StringUtils.substringBeforeLast(getName(file), '.');
String name = getName(file);
if (Files.isDirectory(file)) {
return name;
}
return StringUtils.substringBeforeLast(name, '.');
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for checking if a path is a directory before calling substringBeforeLast is problematic because Files.isDirectory() performs I/O operations and may throw unchecked exceptions (e.g., SecurityException). Additionally, for symbolic links, Files.isDirectory() follows the link by default, which may not be the desired behavior.

More critically, this change assumes that directories should never have their extension stripped, but this breaks the contract for resource pack folders. Resource pack folders don't have extensions to strip, but the original logic would correctly return the folder name. The new logic adds an unnecessary I/O check that could fail or introduce performance issues.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

何意味
这又不是我写的

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (1)

HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java:318

  • AddonUpdateTask performs filesystem operations (local.setOld(true/false), local.markDisabled()) on Schedulers.javafx(). These moves/renames can block the UI noticeably when updating multiple or large files. Consider running these steps on Schedulers.io() (and only switching back to JavaFX for UI notifications) so the update flow stays responsive.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +520 to +541
protected void updateControl(ResourcePackInfoObject item, boolean empty) {
pseudoClassStateChanged(WARNING, false);

if (empty || item == null) return;

this.object = item;
ResourcePackFile file = item.getFile();
imageContainer.setImage(item.getIcon());

content.getTags().clear();
content.setTitle(file.getFileName());
content.setSubtitle(file.getFileNameWithExtension());

FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(file.getFile()));

btnInfo.setOnAction(e -> Controllers.dialog(new ResourcePackInfoDialog(this.page, item)));

if (booleanProperty != null) {
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
}
checkBox.selectedProperty().bindBidirectional(booleanProperty = item.enabledProperty());
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResourcePackListCell.updateControl returns immediately for empty || item == null without unbinding the previous booleanProperty from checkBox.selectedProperty(). With cell virtualization, this can keep the checkbox bound to a stale item and cause leaks/incorrect updates. Unbind/reset bindings (and any item references) when the cell becomes empty.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment on lines 258 to 266
listView.getItems().clear();

Predicate<@Nullable String> predicate;
if (queryString.startsWith("regex:")) {
try {
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
predicate = s -> s != null && pattern.matcher(s).find();
} catch (Throwable e) {
LOG.warning("Illegal regular expression", e);
return;
}
} else {
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
predicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
try {
predicate = StringUtils.compileQuery(queryString);
} catch (Throwable e) {
LOG.warning("Illegal regular expression", e);
return;
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In search(), the list is cleared before the query predicate is compiled. If StringUtils.compileQuery(queryString) throws (invalid regex:), the method returns and leaves the list empty. Compile/validate the predicate before clearing (or repopulate on error) to avoid losing the current results due to a typo in the regex.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment on lines +396 to +404
listView.getItems().clear();

Predicate<@Nullable String> predicate;
try {
predicate = StringUtils.compileQuery(queryString);
} catch (Throwable e) {
LOG.warning("Illegal regular expression", e);
return;
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In search(), the list is cleared before compiling the query predicate. If StringUtils.compileQuery(queryString) throws (e.g., invalid regex:), the method returns early and leaves the list empty. Compile/validate the predicate first (or restore the previous items on error) so a bad regex doesn’t wipe the visible list.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment on lines +36 to +39
public static <T extends Comparable<T>> VersionRange<T> is(T version) {
assert version != null;
return new VersionRange<>(version, version);
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VersionRange.is relies on assert version != null. In production builds where assertions are disabled, is(null) would create a range with both bounds null, which makes isAll() return true and can lead to incorrect range behavior. Prefer an explicit null check (e.g., Objects.requireNonNull(version, ...) or an IllegalArgumentException) to enforce the contract regardless of JVM flags.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

try (var stream = Files.lines(optionsFile, StandardCharsets.UTF_8)) {
stream.forEach(s -> {
if (StringUtils.isNotBlank(s)) {
var entry = s.split(":", 2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么要用 split?我没有看到 options.txt 有选项值中不能包含 : 的要求,像 fullscreenResolution这样的选项值内本来就能有 :

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不是限制了只能分两份了吗,键又不可能有:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

6 participants